summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlberto Lerner <alerner@10gen.com>2013-05-28 19:02:37 -0400
committerAlberto Lerner <alerner@10gen.com>2013-05-30 21:26:36 -0400
commitbb426ee877b62c914931ce49ef12a333f8fabe3e (patch)
tree9fb6fed703de66eca5a558dd603f8f2e0b394343
parentd321ec5375acd7aa0b7abc1c375200c8d955ffb4 (diff)
downloadmongo-bb426ee877b62c914931ce49ef12a333f8fabe3e.tar.gz
SERVER-7175 Initial support for routing updates to the new framework.
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/db/dbhelpers.cpp27
-rw-r--r--src/mongo/db/ops/SConscript46
-rw-r--r--src/mongo/db/ops/modifier_object_replace.cpp116
-rw-r--r--src/mongo/db/ops/modifier_object_replace.h82
-rw-r--r--src/mongo/db/ops/modifier_object_replace_test.cpp28
-rw-r--r--src/mongo/db/ops/modifier_table.cpp144
-rw-r--r--src/mongo/db/ops/modifier_table.h53
-rw-r--r--src/mongo/db/ops/modifier_table_test.cpp47
-rw-r--r--src/mongo/db/ops/update.cpp236
-rw-r--r--src/mongo/db/ops/update.h19
-rw-r--r--src/mongo/db/ops/update_driver.cpp220
-rw-r--r--src/mongo/db/ops/update_driver.h103
-rw-r--r--src/mongo/db/ops/update_driver_test.cpp53
-rw-r--r--src/mongo/db/repl/rs_rollback.cpp27
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) {