diff options
author | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-07-18 15:35:43 -0400 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-07-19 13:27:23 -0400 |
commit | f6fdc8572474e04aee47431f74d4120a6284d44d (patch) | |
tree | ae9b11f73107e2b5852700d80d0455600825fc0a /src/mongo/db/update | |
parent | 57c63852d129681f84c2c35b4078f44e2426f53f (diff) | |
download | mongo-f6fdc8572474e04aee47431f74d4120a6284d44d.tar.gz |
SERVER-28768 Create CompareNode
Diffstat (limited to 'src/mongo/db/update')
-rw-r--r-- | src/mongo/db/update/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/update/compare_node.cpp | 63 | ||||
-rw-r--r-- | src/mongo/db/update/compare_node.h | 63 | ||||
-rw-r--r-- | src/mongo/db/update/compare_node_test.cpp | 783 | ||||
-rw-r--r-- | src/mongo/db/update/modifier_table.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/update/update_object_node_test.cpp | 28 |
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; |