summaryrefslogtreecommitdiff
path: root/src/mongo/db/update
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2017-07-18 15:35:43 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2017-07-19 13:27:23 -0400
commitf6fdc8572474e04aee47431f74d4120a6284d44d (patch)
treeae9b11f73107e2b5852700d80d0455600825fc0a /src/mongo/db/update
parent57c63852d129681f84c2c35b4078f44e2426f53f (diff)
downloadmongo-f6fdc8572474e04aee47431f74d4120a6284d44d.tar.gz
SERVER-28768 Create CompareNode
Diffstat (limited to 'src/mongo/db/update')
-rw-r--r--src/mongo/db/update/SConscript2
-rw-r--r--src/mongo/db/update/compare_node.cpp63
-rw-r--r--src/mongo/db/update/compare_node.h63
-rw-r--r--src/mongo/db/update/compare_node_test.cpp783
-rw-r--r--src/mongo/db/update/modifier_table.cpp5
-rw-r--r--src/mongo/db/update/update_object_node_test.cpp28
6 files changed, 944 insertions, 0 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript
index 13403b00975..b2fc85f4190 100644
--- a/src/mongo/db/update/SConscript
+++ b/src/mongo/db/update/SConscript
@@ -59,6 +59,7 @@ env.Library(
'addtoset_node.cpp',
'arithmetic_node.cpp',
'bit_node.cpp',
+ 'compare_node.cpp',
'modifier_table.cpp',
'path_creating_node.cpp',
'pop_node.cpp',
@@ -86,6 +87,7 @@ env.CppUnitTest(
'addtoset_node_test.cpp',
'arithmetic_node_test.cpp',
'bit_node_test.cpp',
+ 'compare_node_test.cpp',
'pop_node_test.cpp',
'pull_node_test.cpp',
'rename_node_test.cpp',
diff --git a/src/mongo/db/update/compare_node.cpp b/src/mongo/db/update/compare_node.cpp
new file mode 100644
index 00000000000..d42d8be621f
--- /dev/null
+++ b/src/mongo/db/update/compare_node.cpp
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2017 MongoDB 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/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/update/compare_node.h"
+
+#include "mongo/db/query/collation/collator_interface.h"
+
+namespace mongo {
+
+Status CompareNode::init(BSONElement modExpr, const CollatorInterface* collator) {
+ invariant(modExpr.ok());
+ _val = modExpr;
+ setCollator(collator);
+ return Status::OK();
+}
+
+void CompareNode::setCollator(const CollatorInterface* collator) {
+ invariant(!_collator);
+ _collator = collator;
+}
+
+void CompareNode::updateExistingElement(mutablebson::Element* element, bool* noop) const {
+ const auto compareVal = element->compareWithBSONElement(_val, _collator, false);
+ if ((compareVal == 0) || ((_mode == CompareMode::kMax) ? (compareVal > 0) : (compareVal < 0))) {
+ *noop = true;
+ } else {
+ *noop = false;
+ invariantOK(element->setValueBSONElement(_val));
+ }
+}
+
+void CompareNode::setValueForNewElement(mutablebson::Element* element) const {
+ invariantOK(element->setValueBSONElement(_val));
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/update/compare_node.h b/src/mongo/db/update/compare_node.h
new file mode 100644
index 00000000000..0a4cfac3379
--- /dev/null
+++ b/src/mongo/db/update/compare_node.h
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2017 MongoDB 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/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/db/update/path_creating_node.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+/**
+ * Represents the application of $max or $min to the value at the end of a path.
+ */
+class CompareNode : public PathCreatingNode {
+public:
+ enum class CompareMode { kMax, kMin };
+
+ explicit CompareNode(CompareMode mode) : _mode(mode) {}
+
+ Status init(BSONElement modExpr, const CollatorInterface* collator) final;
+
+ std::unique_ptr<UpdateNode> clone() const final {
+ return stdx::make_unique<CompareNode>(*this);
+ }
+
+ void setCollator(const CollatorInterface* collator) final;
+
+protected:
+ void updateExistingElement(mutablebson::Element* element, bool* noop) const final;
+ void setValueForNewElement(mutablebson::Element* element) const final;
+
+private:
+ CompareMode _mode;
+ BSONElement _val;
+ const CollatorInterface* _collator = nullptr;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/update/compare_node_test.cpp b/src/mongo/db/update/compare_node_test.cpp
new file mode 100644
index 00000000000..1f0a9c40c96
--- /dev/null
+++ b/src/mongo/db/update/compare_node_test.cpp
@@ -0,0 +1,783 @@
+/**
+ * Copyright (C) 2017 MongoDB 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/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/update/compare_node.h"
+
+#include "mongo/bson/mutable/algorithm.h"
+#include "mongo/bson/mutable/mutable_bson_test_utils.h"
+#include "mongo/db/json.h"
+#include "mongo/db/query/collation/collator_interface_mock.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace {
+
+using namespace mongo;
+using mongo::mutablebson::Document;
+using mongo::mutablebson::Element;
+using mongo::mutablebson::countChildren;
+
+DEATH_TEST(CompareNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.ok()") {
+ auto update = fromjson("{$max: {}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ node.init(update["$max"].embeddedObject().firstElement(), collator).ignore();
+}
+
+TEST(CompareNodeTest, ApplyMaxSameNumber) {
+ auto update = fromjson("{$max: {a: 1}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMinSameNumber) {
+ auto update = fromjson("{$min: {a: 1}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMaxNumberIsLess) {
+ auto update = fromjson("{$max: {a: 0}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMinNumberIsMore) {
+ auto update = fromjson("{$min: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMaxSameValInt) {
+ auto update = BSON("$max" << BSON("a" << 1LL));
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 1.0}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1.0}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMaxSameValIntZero) {
+ auto update = BSON("$max" << BSON("a" << 0LL));
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 0.0}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 0.0}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMinSameValIntZero) {
+ auto update = BSON("$min" << BSON("a" << 0LL));
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], collator));
+
+ Document doc(fromjson("{a: 0.0}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 0.0}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMissingFieldMinNumber) {
+ auto update = fromjson("{$min: {a: 0}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 0}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyExistingNumberMinNumber) {
+ auto update = fromjson("{$min: {a: 0}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 0}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMissingFieldMaxNumber) {
+ auto update = fromjson("{$max: {a: 0}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root(),
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 0}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyExistingNumberMaxNumber) {
+ auto update = fromjson("{$max: {a: 2}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 1}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 2}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyExistingDateMaxDate) {
+ auto update = fromjson("{$max: {a: {$date: 123123123}}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: {$date: 0}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyExistingEmbeddedDocMaxDoc) {
+ auto update = fromjson("{$max: {a: {b: 3}}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: {b: 2}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyExistingEmbeddedDocMaxNumber) {
+ auto update = fromjson("{$max: {a: 3}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: {b: 2}}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_TRUE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMinRespectsCollation) {
+ auto update = fromjson("{$min: {a: 'dba'}}");
+ const CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], &collator));
+
+ Document doc(fromjson("{a: 'cbc'}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 'dba'}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 'dba'}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMinRespectsCollationFromSetCollator) {
+ auto update = fromjson("{$min: {a: 'dba'}}");
+ const CollatorInterface* binaryComparisonCollator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMin);
+ ASSERT_OK(node.init(update["$min"]["a"], binaryComparisonCollator));
+
+ const CollatorInterfaceMock reverseStringCollator(
+ CollatorInterfaceMock::MockType::kReverseString);
+ node.setCollator(&reverseStringCollator);
+
+ Document doc(fromjson("{a: 'cbc'}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 'dba'}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 'dba'}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyMaxRespectsCollationFromSetCollator) {
+ auto update = fromjson("{$max: {a: 'abd'}}");
+ const CollatorInterface* binaryComparisonCollator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], binaryComparisonCollator));
+
+ const CollatorInterfaceMock reverseStringCollator(
+ CollatorInterfaceMock::MockType::kReverseString);
+ node.setCollator(&reverseStringCollator);
+
+ Document doc(fromjson("{a: 'cbc'}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("a");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_TRUE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 'abd'}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 'abd'}}"), logDoc);
+}
+
+DEATH_TEST(CompareNodeTest, CannotSetCollatorIfCollatorIsNonNull, "Invariant failure !_collator") {
+ auto update = fromjson("{$max: {a: 1}}");
+ const CollatorInterfaceMock caseInsensitiveCollator(
+ CollatorInterfaceMock::MockType::kToLowerString);
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], &caseInsensitiveCollator));
+ node.setCollator(&caseInsensitiveCollator);
+}
+
+DEATH_TEST(CompareNodeTest, CannotSetCollatorTwice, "Invariant failure !_collator") {
+ auto update = fromjson("{$max: {a: 1}}");
+ const CollatorInterface* binaryComparisonCollator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], binaryComparisonCollator));
+
+ const CollatorInterfaceMock caseInsensitiveCollator(
+ CollatorInterfaceMock::MockType::kToLowerString);
+ node.setCollator(&caseInsensitiveCollator);
+ node.setCollator(&caseInsensitiveCollator);
+}
+
+TEST(CompareNodeTest, ApplyIndexesNotAffected) {
+ auto update = fromjson("{$max: {a: 1}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 0}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ UpdateIndexData indexData;
+ indexData.addPath("b");
+ Document logDoc;
+ LogBuilder logBuilder(logDoc.root());
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ &indexData,
+ &logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), logDoc);
+}
+
+TEST(CompareNodeTest, ApplyNoIndexDataOrLogBuilder) {
+ auto update = fromjson("{$max: {a: 1}}");
+ const CollatorInterface* collator = nullptr;
+ CompareNode node(CompareNode::CompareMode::kMax);
+ ASSERT_OK(node.init(update["$max"]["a"], collator));
+
+ Document doc(fromjson("{a: 0}"));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = true;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ LogBuilder* logBuilder = nullptr;
+ auto indexesAffected = false;
+ auto noop = false;
+ node.apply(doc.root()["a"],
+ &pathToCreate,
+ &pathTaken,
+ matchedField,
+ fromReplication,
+ validateForStorage,
+ immutablePaths,
+ indexData,
+ logBuilder,
+ &indexesAffected,
+ &noop);
+ ASSERT_FALSE(noop);
+ ASSERT_FALSE(indexesAffected);
+ ASSERT_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+}
+
+} // namespace
diff --git a/src/mongo/db/update/modifier_table.cpp b/src/mongo/db/update/modifier_table.cpp
index e0028d8b5a2..24cffb3fc47 100644
--- a/src/mongo/db/update/modifier_table.cpp
+++ b/src/mongo/db/update/modifier_table.cpp
@@ -49,6 +49,7 @@
#include "mongo/db/update/addtoset_node.h"
#include "mongo/db/update/arithmetic_node.h"
#include "mongo/db/update/bit_node.h"
+#include "mongo/db/update/compare_node.h"
#include "mongo/db/update/conflict_placeholder_node.h"
#include "mongo/db/update/pop_node.h"
#include "mongo/db/update/pull_node.h"
@@ -195,6 +196,10 @@ std::unique_ptr<UpdateLeafNode> makeUpdateLeafNode(ModifierType modType) {
return stdx::make_unique<ConflictPlaceholderNode>();
case MOD_INC:
return stdx::make_unique<ArithmeticNode>(ArithmeticNode::ArithmeticOp::kAdd);
+ case MOD_MAX:
+ return stdx::make_unique<CompareNode>(CompareNode::CompareMode::kMax);
+ case MOD_MIN:
+ return stdx::make_unique<CompareNode>(CompareNode::CompareMode::kMin);
case MOD_MUL:
return stdx::make_unique<ArithmeticNode>(ArithmeticNode::ArithmeticOp::kMultiply);
case MOD_POP:
diff --git a/src/mongo/db/update/update_object_node_test.cpp b/src/mongo/db/update/update_object_node_test.cpp
index e6bfdeb65ed..b4b369cf14d 100644
--- a/src/mongo/db/update/update_object_node_test.cpp
+++ b/src/mongo/db/update/update_object_node_test.cpp
@@ -176,6 +176,34 @@ TEST(UpdateObjectNodeTest, ValidPopPathParsesSuccessfully) {
foundIdentifiers));
}
+TEST(UpdateObjectNodeTest, ValidMaxPathParsesSuccessfully) {
+ auto update = fromjson("{$max: {'a.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ std::map<StringData, std::unique_ptr<ArrayFilter>> arrayFilters;
+ std::set<std::string> foundIdentifiers;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(&root,
+ modifiertable::ModifierType::MOD_MAX,
+ update["$max"]["a.b"],
+ collator,
+ arrayFilters,
+ foundIdentifiers));
+}
+
+TEST(UpdateObjectNodeTest, ValidMinPathParsesSuccessfully) {
+ auto update = fromjson("{$min: {'a.b': 1}}");
+ const CollatorInterface* collator = nullptr;
+ std::map<StringData, std::unique_ptr<ArrayFilter>> arrayFilters;
+ std::set<std::string> foundIdentifiers;
+ UpdateObjectNode root;
+ ASSERT_OK(UpdateObjectNode::parseAndMerge(&root,
+ modifiertable::ModifierType::MOD_MIN,
+ update["$min"]["a.b"],
+ collator,
+ arrayFilters,
+ foundIdentifiers));
+}
+
TEST(UpdateObjectNodeTest, MultiplePositionalElementsFailToParse) {
auto update = fromjson("{$set: {'a.$.b.$': 5}}");
const CollatorInterface* collator = nullptr;