diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/dbhelpers.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/ops/SConscript | 46 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_object_replace.cpp | 116 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_object_replace.h | 82 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_object_replace_test.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_table.cpp | 144 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_table.h | 53 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_table_test.cpp | 47 | ||||
-rw-r--r-- | src/mongo/db/ops/update.cpp | 236 | ||||
-rw-r--r-- | src/mongo/db/ops/update.h | 19 | ||||
-rw-r--r-- | src/mongo/db/ops/update_driver.cpp | 220 | ||||
-rw-r--r-- | src/mongo/db/ops/update_driver.h | 103 | ||||
-rw-r--r-- | src/mongo/db/ops/update_driver_test.cpp | 53 | ||||
-rw-r--r-- | src/mongo/db/repl/rs_rollback.cpp | 27 |
15 files changed, 1172 insertions, 30 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index a7488efb45a..d36c3ae760a 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -685,6 +685,7 @@ env.StaticLibrary("serveronly", serverOnlyFiles, "db/auth/authmongod", "db/fts/ftsmongod", "db/common", + "db/ops/update_driver", "dbcmdline", "defaultversion", "geoparser", diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index 68c45d9070c..5aa65fb6457 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -213,7 +213,32 @@ namespace mongo { void Helpers::putSingletonGod(const char *ns, BSONObj obj, bool logTheOp) { OpDebug debug; Client::Context context(ns); - _updateObjects(/*god=*/true, ns, obj, /*pattern=*/BSONObj(), /*upsert=*/true, /*multi=*/false , logTheOp , debug ); + + 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 ); + + } + context.getClient()->curop()->done(); } diff --git a/src/mongo/db/ops/SConscript b/src/mongo/db/ops/SConscript index 62953dc26f2..04187f5e3b6 100644 --- a/src/mongo/db/ops/SConscript +++ b/src/mongo/db/ops/SConscript @@ -3,7 +3,7 @@ Import("env") env.StaticLibrary( - target='update-common', + target='update_common', source=[ 'field_checker.cpp', 'path_support.cpp', @@ -21,7 +21,7 @@ env.CppUnitTest( 'field_checker_test.cpp', ], LIBDEPS=[ - 'update-common', + 'update_common', ], ) @@ -32,7 +32,7 @@ env.CppUnitTest( ], LIBDEPS=[ '$BUILD_DIR/mongo/mutable_bson_test_utils', - 'update-common', + 'update_common', ], ) @@ -42,6 +42,7 @@ env.StaticLibrary( 'modifier_add_to_set.cpp', 'modifier_bit.cpp', 'modifier_inc.cpp', + 'modifier_object_replace.cpp', 'modifier_pop.cpp', 'modifier_pull.cpp', 'modifier_pull_all.cpp', @@ -51,7 +52,7 @@ env.StaticLibrary( ], LIBDEPS=[ '$BUILD_DIR/mongo/expressions', - 'update-common', + 'update_common', ], ) @@ -83,6 +84,14 @@ env.CppUnitTest( ) env.CppUnitTest( + target='modifier_object_replace_test', + source='modifier_object_replace_test.cpp', + LIBDEPS=[ + 'update', + ], +) + +env.CppUnitTest( target='modifier_pop_test', source='modifier_pop_test.cpp', LIBDEPS=[ @@ -143,3 +152,32 @@ env.CppUnitTest( 'update', ], ) + +env.StaticLibrary( + target='update_driver', + source=[ + 'modifier_table.cpp', + 'update_driver.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/bson', + '$BUILD_DIR/mongo/db/common', + 'update', + ], +) + +env.CppUnitTest( + target='modifier_table_test', + source='modifier_table_test.cpp', + LIBDEPS=[ + 'update_driver', + ], +) + +env.CppUnitTest( + target='update_driver_test', + source='update_driver_test.cpp', + LIBDEPS=[ + 'update_driver', + ], +) diff --git a/src/mongo/db/ops/modifier_object_replace.cpp b/src/mongo/db/ops/modifier_object_replace.cpp new file mode 100644 index 00000000000..f89c257e286 --- /dev/null +++ b/src/mongo/db/ops/modifier_object_replace.cpp @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2013 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/ops/modifier_object_replace.h" + +#include "mongo/base/error_codes.h" +#include "mongo/bson/mutable/document.h" + +namespace mongo { + + struct ModifierObjectReplace::PreparedState { + + PreparedState(mutablebson::Document* targetDoc) : doc(*targetDoc) {} + + // Document that is going to be changed + mutablebson::Document& doc; + + }; + + ModifierObjectReplace::ModifierObjectReplace() : _val() { + } + + ModifierObjectReplace::~ModifierObjectReplace() { + } + + Status ModifierObjectReplace::init(const BSONElement& modExpr) { + + if (modExpr.type() != Object) { + return Status(ErrorCodes::BadValue, "object replace expects full object"); + } + + BSONObjIterator it(modExpr.embeddedObject()); + while (it.moreWithEOO()) { + BSONElement elem = it.next(); + if (elem.eoo()) { + break; + } + else if (*elem.fieldName() == '$') { + return Status(ErrorCodes::BadValue, "can't mix modifiers and non-modifiers"); + } + } + + // TODO: update timestamps? + + // We make a copy of the object here because the update driver does not guarantees, in + // the case of object replacement, that the modExpr is going to outlive this mod. + _val = modExpr.embeddedObject().getOwned(); + + return Status::OK(); + } + + Status ModifierObjectReplace::prepare(mutablebson::Element root, + const StringData& matchedField, + ExecInfo* execInfo) { + + _preparedState.reset(new PreparedState(&root.getDocument())); + return Status::OK(); + } + + Status ModifierObjectReplace::apply() const { + + // Remove the contents of the provided doc. + mutablebson::Document& doc = _preparedState->doc; + mutablebson::Element current = doc.root().leftChild(); + while (current.ok()) { + mutablebson::Element toRemove = current; + current = current.rightSibling(); + Status status = toRemove.remove(); + if (!status.isOK()) { + return status; + } + } + + // Insert the provided contents instead. + BSONObjIterator it(_val); + while (it.more()) { + BSONElement elem = it.next(); + Status status = doc.root().appendElement(elem); + if (!status.isOK()) { + return status; + } + } + + return Status::OK(); + } + + Status ModifierObjectReplace::log(mutablebson::Element logRoot) const { + + // We'd like to create an entry such as {<object replacement>} under 'logRoot'. + mutablebson::Document& doc = logRoot.getDocument(); + BSONObjIterator it(_val); + while (it.more()) { + BSONElement elem = it.next(); + Status status = doc.root().appendElement(elem); + if (!status.isOK()) { + return status; + } + } + + return Status::OK(); + } + +} // namespace mongo diff --git a/src/mongo/db/ops/modifier_object_replace.h b/src/mongo/db/ops/modifier_object_replace.h new file mode 100644 index 00000000000..edead81eeb1 --- /dev/null +++ b/src/mongo/db/ops/modifier_object_replace.h @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2013 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/>. + */ + +#pragma once + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/bson/mutable/element.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/ops/modifier_interface.h" + +namespace mongo { + + class ModifierObjectReplace : public ModifierInterface { + MONGO_DISALLOW_COPYING(ModifierObjectReplace); + + public: + + ModifierObjectReplace(); + + // + // Modifier interface implementation + // + + virtual ~ModifierObjectReplace(); + + + /** + * Returns true and takes the embedded object contained in 'modExpr' to be the object + * we're replacing for. The field name of 'modExpr' is ignored. If 'modExpr' is in an + * unexpected format or if it can't be parsed for some reason, returns an error status + * describing the error. + */ + virtual Status init(const BSONElement& modExpr); + + /** + * Registers the that 'root' is in the document that we want to fully replace. + * prepare() returns OK and always fills 'execInfo' with false for inPlace and true for + * noOp. + */ + virtual Status prepare(mutablebson::Element root, + const StringData& matchedField, + ExecInfo* execInfo); + + /** + * Replaces the document passed in prepare() for the object passed in init(). Returns + * OK if successful or a status describing the error. + */ + virtual Status apply() const; + + /** + * Adds a log entry to logRoot corresponding to full object replacement. Returns OK if + * successful or a status describing the error. + */ + virtual Status log(mutablebson::Element logRoot) const; + + private: + + // Object to replace with. + BSONObj _val; + + // The document whose value needs to be replaced. This state is valid after a prepare() + // was issued and until a log() is issued. The document this mod is being prepared + // against must e live throughout all the calls. + struct PreparedState; + scoped_ptr<PreparedState> _preparedState; + }; + +} // namespace mongo diff --git a/src/mongo/db/ops/modifier_object_replace_test.cpp b/src/mongo/db/ops/modifier_object_replace_test.cpp new file mode 100644 index 00000000000..b2e7dc8d5ce --- /dev/null +++ b/src/mongo/db/ops/modifier_object_replace_test.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2013 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/ops/modifier_object_replace.h" + +#include "mongo/unittest/unittest.h" + +namespace { + + TEST(TODO, PlaceHolder) { + ASSERT(true); + } + +} // unnamed namespace diff --git a/src/mongo/db/ops/modifier_table.cpp b/src/mongo/db/ops/modifier_table.cpp new file mode 100644 index 00000000000..f250dfaac43 --- /dev/null +++ b/src/mongo/db/ops/modifier_table.cpp @@ -0,0 +1,144 @@ +/** + * Copyright (C) 2013 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/ops/modifier_table.h" + +#include <string> +#include <utility> + +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/db/ops/modifier_add_to_set.h" +#include "mongo/db/ops/modifier_bit.h" +#include "mongo/db/ops/modifier_inc.h" +#include "mongo/db/ops/modifier_pop.h" +#include "mongo/db/ops/modifier_pull.h" +#include "mongo/db/ops/modifier_pull_all.h" +#include "mongo/db/ops/modifier_push.h" +#include "mongo/db/ops/modifier_set.h" +#include "mongo/db/ops/modifier_unset.h" +#include "mongo/platform/unordered_map.h" + +namespace mongo { +namespace modifiertable { + + namespace { + + struct ModifierEntry { + string name; + ModifierType type; + + ModifierEntry(const StringData& name, ModifierType type) + : name(name.toString()) + , type(type) { + } + }; + + typedef unordered_map<StringData, ModifierEntry, StringData::Hasher> NameMap; + + NameMap* MODIFIER_NAME_MAP; + + void init(NameMap* nameMap) { + ModifierEntry entryAddToSet("$addToSet", MOD_ADD_TO_SET); + nameMap->insert(make_pair(StringData(entryAddToSet.name), entryAddToSet)); + + ModifierEntry entryBit("$bit", MOD_BIT); + nameMap->insert(make_pair(StringData(entryBit.name), entryBit)); + + ModifierEntry entryPop("$pop", MOD_POP); + nameMap->insert(make_pair(StringData(entryPop.name), entryPop)); + + ModifierEntry entryPull("$pull", MOD_PULL); + nameMap->insert(make_pair(StringData(entryPull.name), entryPull)); + + ModifierEntry entryPullAll("$pullAll", MOD_PULL_ALL); + nameMap->insert(make_pair(StringData(entryPullAll.name), entryPullAll)); + + ModifierEntry entryPush("$push", MOD_PUSH); + nameMap->insert(make_pair(StringData(entryPush.name), entryPush)); + + ModifierEntry entryPushAll("$pushAll", MOD_PUSH_ALL); + nameMap->insert(make_pair(StringData(entryPushAll.name), entryPushAll)); + + ModifierEntry entryInc("$inc", MOD_INC); + nameMap->insert(make_pair(StringData(entryInc.name), entryInc)); + + ModifierEntry entrySet("$set", MOD_SET); + nameMap->insert(make_pair(StringData(entrySet.name), entrySet)); + + ModifierEntry entrySetOnInsert("$setOnInsert", MOD_SET_ON_INSERT); + nameMap->insert(make_pair(StringData(entrySetOnInsert.name), entrySetOnInsert)); + + ModifierEntry entryRename("$rename", MOD_RENAME); + nameMap->insert(make_pair(StringData(entryRename.name), entryRename)); + + ModifierEntry entryUnset("$unset", MOD_UNSET); + nameMap->insert(make_pair(StringData(entryUnset.name), entryUnset)); + } + + } // unnamed namespace + + MONGO_INITIALIZER(ModifierTable)(InitializerContext* context) { + MODIFIER_NAME_MAP = new NameMap; + init(MODIFIER_NAME_MAP); + + return Status::OK(); + } + + ModifierType getType(const StringData& typeStr) { + NameMap::const_iterator it = MODIFIER_NAME_MAP->find(typeStr); + if (it == MODIFIER_NAME_MAP->end()) { + return MOD_UNKNOWN; + } + return it->second.type; + } + + ModifierInterface* makeUpdateMod(ModifierType modType) { + switch (modType) { + case MOD_ADD_TO_SET: + return new ModifierAddToSet; + case MOD_BIT: + return new ModifierBit; + case MOD_INC: + return new ModifierInc; + case MOD_POP: + return new ModifierPop; + case MOD_PULL: + return new ModifierPull; + case MOD_PULL_ALL: + return new ModifierPullAll; + case MOD_PUSH: + return new ModifierPush; + case MOD_PUSH_ALL: + verify(false); + return NULL; // TODO (syntactic sugar) + case MOD_SET: + return new ModifierSet; + case MOD_SET_ON_INSERT: + verify(false); + return NULL; // TODO (syntactic sugar) + case MOD_RENAME: + verify(false); + return NULL; // TODO + case MOD_UNSET: + return new ModifierUnset; + default: + return NULL; + } + } + +} // namespace modifiertable +} // namespace mongo diff --git a/src/mongo/db/ops/modifier_table.h b/src/mongo/db/ops/modifier_table.h new file mode 100644 index 00000000000..cd25dc552bb --- /dev/null +++ b/src/mongo/db/ops/modifier_table.h @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2013 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/>. + */ + +#pragma once + +#include "mongo/db/ops/modifier_interface.h" + +namespace mongo { +namespace modifiertable { + + enum ModifierType { + MOD_ADD_TO_SET, + MOD_BIT, + MOD_INC, + MOD_POP, + MOD_PULL, + MOD_PULL_ALL, + MOD_PUSH, + MOD_PUSH_ALL, + MOD_SET, + MOD_SET_ON_INSERT, + MOD_RENAME, + MOD_UNSET, + MOD_UNKNOWN + }; + + /** + * Returns the modifier type for 'typeStr', if it was recognized as an existing update + * mod, or MOD_UNKNOWN otherwise. + */ + ModifierType getType(const StringData& typeStr); + + /** + * Instantiate an update mod that corresponds to 'modType' or NULL if 'modType' is not + * valid. The ownership of the new object is the caller's. + */ + ModifierInterface* makeUpdateMod(ModifierType modType); + +} // namespace modifiertable +} // namespace mongo diff --git a/src/mongo/db/ops/modifier_table_test.cpp b/src/mongo/db/ops/modifier_table_test.cpp new file mode 100644 index 00000000000..77c6e869eca --- /dev/null +++ b/src/mongo/db/ops/modifier_table_test.cpp @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2013 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/ops/modifier_table.h" + +#include <memory> + +#include "mongo/db/ops/modifier_interface.h" +#include "mongo/unittest/unittest.h" + +namespace { + + using namespace mongo::modifiertable; + + using mongo::ModifierInterface; + using std::auto_ptr; + + TEST(getType, Normal) { + ASSERT_EQUALS(getType("$set"), MOD_SET); + ASSERT_EQUALS(getType("$AModThatDoesn'tExist"), MOD_UNKNOWN); + ASSERT_EQUALS(getType("NotAModExpression"), MOD_UNKNOWN); + } + + TEST(makeUpdateMod, Normal) { + auto_ptr<ModifierInterface> mod; + + mod.reset(makeUpdateMod(MOD_SET)); + ASSERT_NOT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0)); + + mod.reset(makeUpdateMod(MOD_UNKNOWN)); + ASSERT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0)); + } + +} // unnamed namespace diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp index 7135241818e..91806cdac88 100644 --- a/src/mongo/db/ops/update.cpp +++ b/src/mongo/db/ops/update.cpp @@ -23,6 +23,8 @@ #include "mongo/client/dbclientinterface.h" #include "mongo/db/clientcursor.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" @@ -30,13 +32,19 @@ #include "mongo/db/queryutil.h" #include "mongo/db/record.h" #include "mongo/db/repl/oplog.h" -#include "mongo/db/ops/update_internal.h" +#include "mongo/db/server_parameters.h" //#define DEBUGUPDATE(x) cout << x << endl; #define DEBUGUPDATE(x) namespace mongo { + MONGO_EXPORT_SERVER_PARAMETER( newUpdateFrameworkEnabled, bool, false ); + + bool isNewUpdateFrameworkEnabled() { + return false; + } + void checkNoMods( BSONObj o ) { BSONObjIterator i( o ); while( i.moreWithEOO() ) { @@ -463,6 +471,135 @@ namespace mongo { } } + 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 + // + Separate UpdateParser from UpdateRunner (the latter should be "stage-y") + // + fast path for update for query by _id + // + $atomic support (or better, support proper yielding if not) + // + define the dedup story (and do it here, if that's the decidion) + // + support in-place updates (and determination if indices are involved in an update) + // + update OpDebug counters properly + // + specific paths set on insert + // + support for relaxing viable path constraint in replication + // + set UpdateResponse properly + + UpdateDriver::Options opts; + opts.multi = multi; + opts.upsert = upsert; + opts.logOp = logop; + UpdateDriver driver(opts); + Status status = driver.parse( updateobj ); + if ( !status.isOK() ) { + uasserted( 16840, status.reason() ); + } + + shared_ptr<Cursor> cursor = getOptimizedCursor( ns, patternOrig, BSONObj(), planPolicy ); + NamespaceDetails* d = nsdetails(ns); // can be null if an upsert... + NamespaceDetailsTransient* nsdt = &NamespaceDetailsTransient::get(ns); + + // We may or may not have documents for this update. If we don't, then try to upsert, + // if allowed. + if ( !cursor->ok() && upsert ) { + + // If this is not a full object replace, we need to generate a document by + // examining the query. Otherwise, we can use the replacement object itself. + BSONObj oldObj; + if ( *updateobj.firstElementFieldName() == '$' ) { + if ( !driver.createFromQuery( patternOrig, &oldObj ) ) { + uasserted( 16835, "cannot create object to update" ); + } + // TODO this is the hook for activating a $setOnInsert + } + + // Since this is an upsert, we will be oplogging it as an insert. We don't + // need the driver's help to build the oplog record, then. + driver.setLogOp(false); + + BSONObj newObj; + status = driver.update( oldObj, StringData(), &newObj, NULL /* no oplog record */); + if ( !status.isOK() ) { + uasserted( 16836, status.reason() ); + } + + theDataFileMgr.insertWithObjMod( ns, newObj, false, su ); + + if ( logop ) { + logOp( "i", ns, newObj, 0, 0, fromMigrate, &newObj ); + } + + return UpdateResult( false, false, 0, BSONObj() ); + } + + // We have documents for this update. Let's fetch each of them and pipe them through + // the update expression. + while ( cursor->ok() ) { + + // Get Obj and match details + Record* r = cursor->_current(); + DiskLoc loc = cursor->currLoc(); + const BSONObj oldObj = loc.obj(); + + + // Skip documents that don't match. Also, we don't want to update the very object + // atop of which the cursor is, becuase that document may move. So we advance the + // cursor before operating on the record. + MatchDetails matchDetails; + matchDetails.requestElemMatchKey(); + if ( !cursor->currentMatches( &matchDetails ) ) { + cursor->advance(); + continue; + } + else if (multi) { + cursor->advance(); + } + + BSONObj newObj; + BSONObj logObj; + status = driver.update( oldObj, matchDetails.elemMatchKey(), &newObj, &logObj ); + if ( !status.isOK() ) { + uasserted( 16837, status.reason() ); + } + + // Write Obj + theDataFileMgr.updateRecord(ns, + d, + nsdt, + r, + loc, + newObj.objdata(), + newObj.objsize(), + debug); + + // Log Obj + if ( logop ) { + if ( !logObj.isEmpty() ) { + BSONObj pattern = patternOrig; + logOp("u", ns, logObj , &pattern, 0, fromMigrate, &newObj ); + } + } + + getDur().commitIfNeeded(); + + if (!multi) { + break; + } + } + + return UpdateResult( false, false, 0, BSONObj() ); + } + UpdateResult updateObjects( const char* ns, const BSONObj& updateobj, const BSONObj& patternOrig, @@ -475,11 +612,22 @@ namespace mongo { validateUpdate( ns , updateobj , patternOrig ); - UpdateResult ur = _updateObjects(false, ns, updateobj, patternOrig, - upsert, multi, logop, - debug, NULL, fromMigrate, planPolicy ); - debug.nupdated = ur.num; - return ur; + if ( newUpdateFrameworkEnabled ) { + + 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 updateObjectsForReplication( const char* ns, @@ -494,25 +642,67 @@ namespace mongo { validateUpdate( ns , updateobj , patternOrig ); - 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; + if ( newUpdateFrameworkEnabled ) { + + 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; + + } } BSONObj applyUpdateOperators( const BSONObj& from, const BSONObj& operators ) { - ModSet mods( operators ); - return mods.prepare( from, false /* not an insertion */ )->createNewFromMods(); + if ( newUpdateFrameworkEnabled ) { + UpdateDriver::Options opts; + opts.multi = false; + opts.upsert = false; + UpdateDriver driver(opts); + Status status = driver.parse( operators ); + if ( !status.isOK() ) { + uasserted( 16838, status.reason() ); + } + + BSONObj newObj; + status = driver.update( from, StringData(), &newObj, NULL /* not oplogging */ ); + if ( !status.isOK() ) { + uasserted( 16839, status.reason() ); + } + + return newObj; + } + else { + ModSet mods( operators ); + return mods.prepare( from, false /* not an insertion */ )->createNewFromMods(); + } } - + } // namespace mongo diff --git a/src/mongo/db/ops/update.h b/src/mongo/db/ops/update.h index 38ffe3e9c4e..35e70634d49 100644 --- a/src/mongo/db/ops/update.h +++ b/src/mongo/db/ops/update.h @@ -44,6 +44,9 @@ namespace mongo { class RemoveSaver; + /** Returns true if updates are supposed to be handle by the new update framework */ + bool isNewUpdateFrameworkEnabled(); + /* 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) @@ -85,9 +88,23 @@ namespace mongo { OpDebug& debug, RemoveSaver* rs = 0, bool fromMigrate = false, - const QueryPlanSelectionPolicy& planPolicy = QueryPlanSelectionPolicy::any(), + const QueryPlanSelectionPolicy& planPolicy + = 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); /** * takes the from document and returns a new document diff --git a/src/mongo/db/ops/update_driver.cpp b/src/mongo/db/ops/update_driver.cpp new file mode 100644 index 00000000000..0aa1a01237e --- /dev/null +++ b/src/mongo/db/ops/update_driver.cpp @@ -0,0 +1,220 @@ +/** + * Copyright (C) 2013 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/ops/update_driver.h" + +#include "mongo/base/error_codes.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/mutable/document.h" +#include "mongo/db/field_ref.h" +#include "mongo/db/ops/modifier_interface.h" +#include "mongo/db/ops/modifier_object_replace.h" +#include "mongo/db/ops/modifier_table.h" +#include "mongo/util/embedded_builder.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + UpdateDriver::UpdateDriver(const Options& opts) + : _multi(opts.multi) + , _upsert(opts.upsert) + , _logOp(opts.logOp) { + } + + UpdateDriver::~UpdateDriver() { + clear(); + } + + Status UpdateDriver::parse(const BSONObj& updateExpr) { + clear(); + + // Check if the update expression is a full object replacement. + if (*updateExpr.firstElementFieldName() != '$') { + if (_multi) { + return Status(ErrorCodes::FailedToParse, + "multi update only works with $ operators"); + } + + // Modifiers expect BSONElements as input. But the input to object replace is, by + // definition, an object. We wrap the 'updateExpr' as the mod is expecting. Note + // that the wrapper is temporary so the object replace mod should make a copy of + // the object. + auto_ptr<ModifierObjectReplace> mod(new ModifierObjectReplace); + BSONObj wrapper = BSON( "dummy" << updateExpr ); + Status status = mod->init(wrapper.firstElement()); + if (!status.isOK()) { + return status; + } + + _mods.push_back(mod.release()); + + return Status::OK(); + } + + // The update expression is made of mod operators, that is + // { <$mod>: {...}, <$mod>: {...}, ... } + BSONObjIterator outerIter(updateExpr); + while (outerIter.more()) { + BSONElement outerModElem = outerIter.next(); + + modifiertable::ModifierType modType = modifiertable::getType(outerModElem.fieldName()); + if (modType == modifiertable::MOD_UNKNOWN) { + return Status(ErrorCodes::FailedToParse, "wrong modifier type"); + } + + BSONObjIterator innerIter(outerModElem.embeddedObject()); + while (innerIter.more()) { + BSONElement innerModElem = innerIter.next(); + + auto_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(modType)); + dassert(mod.get()); + + if (innerModElem.eoo()) { + return Status(ErrorCodes::FailedToParse, "empty mod"); + } + + Status status = mod->init(innerModElem); + if (!status.isOK()) { + return status; + } + + _mods.push_back(mod.release()); + } + } + + return Status::OK(); + } + + bool UpdateDriver::createFromQuery(const BSONObj query, BSONObj* newObj) const { + // TODO + // This moved from ModSet::createNewFromQuery + // Check if it can be streamlined + 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 (mongoutils::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(); + return true; + } + + Status UpdateDriver::update(const BSONObj& oldObj, + const StringData& matchedField, + BSONObj* newObj, + BSONObj* logOpRec) { + // TODO: assert that update() is called at most once in a !_multi case. + + mutablebson::Document doc(oldObj); + + for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) { + ModifierInterface::ExecInfo execInfo; + Status status = (*it)->prepare(doc.root(), matchedField, &execInfo); + if (!status.isOK()) { + return status; + } + + // TODO: gather the fields that each mod is interested on and check for conflicts. + // for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) { + // if (execInfo.fieldRef[i] == 0) { + // break; + // } + // } + + if (!execInfo.noOp) { + status = (*it)->apply(); + if (!status.isOK()) { + return status; + } + } + } + + if (_logOp && logOpRec) { + mutablebson::Document logDoc; + for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) { + Status status = (*it)->log(logDoc.root()); + if (!status.isOK()) { + return status; + } + } + *logOpRec = logDoc.getObject(); + } + + *newObj = doc.getObject(); + return Status::OK(); + } + + size_t UpdateDriver::numMods() const { + return _mods.size(); + } + + bool UpdateDriver::multi() const { + return _multi; + } + + void UpdateDriver::setMulti(bool multi) { + _multi = multi; + } + + bool UpdateDriver::upsert() const { + return _upsert; + } + + void UpdateDriver::setUpsert(bool upsert) { + _upsert = upsert; + } + + bool UpdateDriver::logOp() const { + return _logOp; + } + + void UpdateDriver::setLogOp(bool logOp) { + _logOp = logOp; + } + + void UpdateDriver::clear() { + for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) { + delete *it; + } + } + +} // namespace mongo diff --git a/src/mongo/db/ops/update_driver.h b/src/mongo/db/ops/update_driver.h new file mode 100644 index 00000000000..db3b6a224ac --- /dev/null +++ b/src/mongo/db/ops/update_driver.h @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2013 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/>. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/base/status.h" +#include "mongo/db/jsobj.h" + +namespace mongo { + + class ModifierInterface; + + class UpdateDriver { + public: + + struct Options; + UpdateDriver(const Options& opts); + + ~UpdateDriver(); + + /** + * Returns OK and fills in '_mods' if 'updateExpr' is correct. Otherwise returns + * an error status with a corresponding description. + */ + Status parse(const BSONObj& updateExpr); + + /** + * Returns true and derives a BSONObj, 'newObj', from 'query'. + * + * TODO: Elaborate on the logic used here. + */ + bool createFromQuery(const BSONObj query, BSONObj* newObj) const; + + /** + * Returns OK and executes '_mods' over 'oldObj', generating 'newObj'. If any mod is + * positional, use 'matchedField' (index of the array item matched). If the driver's + * '_logOp' mode is turned on, and if 'logOpRec' is not NULL, fills in the latter with + * the oplog entry corresponding to the update. If '_mods' can't be applied, returns + * an error status with a corresponding description. + */ + Status update(const BSONObj& oldObj, + const StringData& matchedField, + BSONObj* newObj, + BSONObj* logOpRec); + + // + // Accessors + // + + size_t numMods() const; + + bool multi() const; + void setMulti(bool multi); + + bool upsert() const; + void setUpsert(bool upsert); + + bool logOp() const; + void setLogOp(bool logOp); + + private: + + /** Resets the state of the class associated with mods (not the error state) */ + void clear(); + + // May this driver apply updates to several documents? + bool _multi; + + // May this driver construct a new object if an update for a non-existing one is sent? + bool _upsert; + + // Should this driver generate an oplog record when it applies the update? + bool _logOp; + + // Collection of update mod instances. Owned here. + vector<ModifierInterface*> _mods; + }; + + struct UpdateDriver::Options { + bool multi; + bool upsert; + bool logOp; + + Options() : multi(false), upsert(false), logOp(false) {} + }; + +} // namespace mongo diff --git a/src/mongo/db/ops/update_driver_test.cpp b/src/mongo/db/ops/update_driver_test.cpp new file mode 100644 index 00000000000..52dd9758116 --- /dev/null +++ b/src/mongo/db/ops/update_driver_test.cpp @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2013 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/ops/update_driver.h" + +#include "mongo/unittest/unittest.h" +#include "mongo/db/json.h" + +namespace { + + using mongo::fromjson; + using mongo::UpdateDriver; + + TEST(Parse, Normal) { + UpdateDriver::Options opts; + UpdateDriver driver(opts); + ASSERT_OK(driver.parse(fromjson("{$set:{a:1}}"))); + ASSERT_EQUALS(driver.numMods(), 1U); + } + + TEST(Parse, MultiMods) { + UpdateDriver::Options opts; + UpdateDriver driver(opts); + ASSERT_OK(driver.parse(fromjson("{$set:{a:1, b:1}}"))); + ASSERT_EQUALS(driver.numMods(), 2U); + } + + TEST(Parse, EmptyMods) { + UpdateDriver::Options opts; + UpdateDriver driver(opts); + ASSERT_OK(driver.parse(fromjson("{$set:{}}"))); + } + + TEST(Parse, WrongType) { + UpdateDriver::Options opts; + UpdateDriver driver(opts); + ASSERT_OK(driver.parse(fromjson("{$set:[{a:1}]}"))); + } + +} // unnamed namespace diff --git a/src/mongo/db/repl/rs_rollback.cpp b/src/mongo/db/repl/rs_rollback.cpp index 5adeec4cc66..a04d08bfdd9 100644 --- a/src/mongo/db/repl/rs_rollback.cpp +++ b/src/mongo/db/repl/rs_rollback.cpp @@ -550,7 +550,32 @@ namespace mongo { // todo faster... OpDebug debug; updates++; - _updateObjects(/*god*/true, d.ns, i->second, pattern, /*upsert=*/true, /*multi=*/false , /*logtheop=*/false , debug, rs.get() ); + 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()); + + } } } catch(DBException& e) { |