summaryrefslogtreecommitdiff
path: root/src/mongo/db/update
diff options
context:
space:
mode:
authorJustin Seyster <justin.seyster@mongodb.com>2017-07-10 11:27:20 -0400
committerJustin Seyster <justin.seyster@mongodb.com>2017-07-10 14:04:21 -0400
commit88c47189681b3212fe7ad98e4097b7e00b1aa232 (patch)
treea195768fa6cc198a58e8f4c6f2fd48c20b95602e /src/mongo/db/update
parent758d9e2c61e605f0a5bb9cc098a9771a74aa9fda (diff)
downloadmongo-88c47189681b3212fe7ad98e4097b7e00b1aa232.tar.gz
SERVER-28765 Create BitNode.
Additionally, this change fixes a couple of minor things. First, it makes the single-argument constructor for ArithmeticNode explicit. Second, it adds a (noop = false) assignment in the else branch of a test in set_node.cpp, instead of relying on the fact that callers set noop to false. That gives SetNode clearer behavior.
Diffstat (limited to 'src/mongo/db/update')
-rw-r--r--src/mongo/db/update/SConscript2
-rw-r--r--src/mongo/db/update/arithmetic_node.h2
-rw-r--r--src/mongo/db/update/bit_node.cpp134
-rw-r--r--src/mongo/db/update/bit_node.h67
-rw-r--r--src/mongo/db/update/bit_node_test.cpp471
-rw-r--r--src/mongo/db/update/modifier_table.cpp3
-rw-r--r--src/mongo/db/update/set_node.cpp1
7 files changed, 679 insertions, 1 deletions
diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript
index 4468b4c681d..c1496809662 100644
--- a/src/mongo/db/update/SConscript
+++ b/src/mongo/db/update/SConscript
@@ -58,6 +58,7 @@ env.Library(
source=[
'addtoset_node.cpp',
'arithmetic_node.cpp',
+ 'bit_node.cpp',
'modifier_table.cpp',
'path_creating_node.cpp',
'pop_node.cpp',
@@ -82,6 +83,7 @@ env.CppUnitTest(
source=[
'addtoset_node_test.cpp',
'arithmetic_node_test.cpp',
+ 'bit_node_test.cpp',
'pop_node_test.cpp',
'rename_node_test.cpp',
'set_node_test.cpp',
diff --git a/src/mongo/db/update/arithmetic_node.h b/src/mongo/db/update/arithmetic_node.h
index 5f4b5840272..e8f0917443d 100644
--- a/src/mongo/db/update/arithmetic_node.h
+++ b/src/mongo/db/update/arithmetic_node.h
@@ -40,7 +40,7 @@ class ArithmeticNode : public PathCreatingNode {
public:
enum class ArithmeticOp { kAdd, kMultiply };
- ArithmeticNode(ArithmeticOp op) : _op(op) {}
+ explicit ArithmeticNode(ArithmeticOp op) : _op(op) {}
Status init(BSONElement modExpr, const CollatorInterface* collator) final;
diff --git a/src/mongo/db/update/bit_node.cpp b/src/mongo/db/update/bit_node.cpp
new file mode 100644
index 00000000000..57fbb57aa2b
--- /dev/null
+++ b/src/mongo/db/update/bit_node.cpp
@@ -0,0 +1,134 @@
+/**
+ * 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/bit_node.h"
+
+#include "mongo/bson/mutable/algorithm.h"
+
+namespace mongo {
+
+Status BitNode::init(BSONElement modExpr, const CollatorInterface* collator) {
+ invariant(modExpr.ok());
+
+ if (modExpr.type() != mongo::Object) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "The $bit modifier is not compatible with a "
+ << typeName(modExpr.type())
+ << ". You must pass in an embedded document: "
+ "{$bit: {field: {and/or/xor: #}}");
+ }
+
+ for (const auto& curOp : modExpr.embeddedObject()) {
+ const StringData payloadFieldName = curOp.fieldNameStringData();
+
+ BitwiseOp parsedOp;
+ if (payloadFieldName == "and") {
+ parsedOp.bitOperator = &SafeNum::bitAnd;
+ } else if (payloadFieldName == "or") {
+ parsedOp.bitOperator = &SafeNum::bitOr;
+ } else if (payloadFieldName == "xor") {
+ parsedOp.bitOperator = &SafeNum::bitXor;
+ } else {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "The $bit modifier only supports 'and', 'or', and 'xor', not '"
+ << payloadFieldName
+ << "' which is an unknown operator: {"
+ << curOp
+ << "}");
+ }
+
+ if ((curOp.type() != mongo::NumberInt) && (curOp.type() != mongo::NumberLong)) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "The $bit modifier field must be an Integer(32/64 bit); a '"
+ << typeName(curOp.type())
+ << "' is not supported here: {"
+ << curOp
+ << "}");
+ }
+
+ parsedOp.operand = SafeNum(curOp);
+ _opList.push_back(parsedOp);
+ }
+
+ if (_opList.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "You must pass in at least one bitwise operation. "
+ << "The format is: "
+ "{$bit: {field: {and/or/xor: #}}");
+ }
+
+ return Status::OK();
+}
+
+void BitNode::updateExistingElement(mutablebson::Element* element, bool* noop) const {
+ if (!element->isIntegral()) {
+ mutablebson::Element idElem =
+ mutablebson::findFirstChildNamed(element->getDocument().root(), "_id");
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "Cannot apply $bit to a value of non-integral type."
+ << idElem.toString()
+ << " has the field "
+ << element->getFieldName()
+ << " of non-integer type "
+ << typeName(element->getType()));
+ }
+
+ SafeNum value = applyOpList(element->getValueSafeNum());
+
+ if (!value.isIdentical(element->getValueSafeNum())) {
+ *noop = false;
+ invariantOK(element->setValueSafeNum(value));
+ } else {
+ *noop = true;
+ }
+}
+
+void BitNode::setValueForNewElement(mutablebson::Element* element) const {
+ SafeNum value = applyOpList(SafeNum(static_cast<int32_t>(0)));
+ invariantOK(element->setValueSafeNum(value));
+}
+
+SafeNum BitNode::applyOpList(SafeNum value) const {
+ for (const auto& op : _opList) {
+ value = (value.*(op.bitOperator))(op.operand);
+
+ if (!value.isValid()) {
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "Failed to apply $bit operations to current value: "
+ << value.debugString());
+ }
+ }
+
+ return value;
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/update/bit_node.h b/src/mongo/db/update/bit_node.h
new file mode 100644
index 00000000000..98e89e47aa6
--- /dev/null
+++ b/src/mongo/db/update/bit_node.h
@@ -0,0 +1,67 @@
+/**
+ * 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 a $bit to the value at the end of a path.
+ */
+class BitNode : public PathCreatingNode {
+public:
+ Status init(BSONElement modExpr, const CollatorInterface* collator) final;
+
+ std::unique_ptr<UpdateNode> clone() const final {
+ return stdx::make_unique<BitNode>(*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:
+ /**
+ * Applies each op in "_opList" to "value" and returns the result.
+ */
+ SafeNum applyOpList(SafeNum value) const;
+
+ struct BitwiseOp {
+ SafeNum (SafeNum::*bitOperator)(const SafeNum&) const;
+ SafeNum operand;
+ };
+
+ std::vector<BitwiseOp> _opList;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/update/bit_node_test.cpp b/src/mongo/db/update/bit_node_test.cpp
new file mode 100644
index 00000000000..518b41ab048
--- /dev/null
+++ b/src/mongo/db/update/bit_node_test.cpp
@@ -0,0 +1,471 @@
+/**
+ * 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/bit_node.h"
+
+#include "mongo/bson/mutable/algorithm.h"
+#include "mongo/bson/mutable/mutable_bson_test_utils.h"
+#include "mongo/db/json.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;
+
+TEST(BitNodeTest, InitWithDoubleFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: 0}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithStringFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: ''}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithArrayFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: []}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithEmptyDocumentFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: {}}}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithUnknownOperatorFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: {foo: 4}}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithArrayArgumentToOperatorFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: {or: []}}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithStringArgumentToOperatorFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: {or: 'foo'}}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithDoubleArgumentToOperatorFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: {or: 1.0}}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, InitWithDecimalArgumentToOperatorFails) {
+ const CollatorInterface* collator = nullptr;
+ auto update = fromjson("{$bit: {a: {or: NumberDecimal(\"1.0\")}}}");
+ BitNode node;
+ ASSERT_NOT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ParsesAndInt) {
+ auto update = fromjson("{$bit: {a: {and: NumberInt(1)}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ParsesOrInt) {
+ auto update = fromjson("{$bit: {a: {or: NumberInt(1)}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ParsesXorInt) {
+ auto update = fromjson("{$bit: {a: {xor: NumberInt(1)}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ParsesAndLong) {
+ auto update = fromjson("{$bit: {a: {and: NumberLong(1)}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ParsesOrLong) {
+ auto update = fromjson("{$bit: {a: {or: NumberLong(1)}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ParsesXorLong) {
+ auto update = fromjson("{$bit: {a: {xor: NumberLong(1)}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+}
+
+TEST(BitNodeTest, ApplyAndLogEmptyDocumentAnd) {
+ auto update = fromjson("{$bit: {a: {and: 1}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(fromjson("{a: 0}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 0}}"), logDoc);
+}
+
+TEST(BitNodeTest, ApplyAndLogEmptyDocumentOr) {
+ auto update = fromjson("{$bit: {a: {or: 1}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), logDoc);
+}
+
+TEST(BitNodeTest, ApplyAndLogEmptyDocumentXor) {
+ auto update = fromjson("{$bit: {a: {xor: 1}}}");
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(fromjson("{}"));
+ FieldRef pathToCreate("a");
+ FieldRef pathTaken("");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(fromjson("{a: 1}"), doc);
+ ASSERT_FALSE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{$set: {a: 1}}"), logDoc);
+}
+
+TEST(BitNodeTest, ApplyAndLogSimpleDocumentAnd) {
+ auto update = BSON("$bit" << BSON("a" << BSON("and" << 0b0110)));
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(BSON("a" << 0b0101));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(BSON("a" << 0b0100), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0100)), logDoc);
+}
+
+TEST(BitNodeTest, ApplyAndLogSimpleDocumentOr) {
+ auto update = BSON("$bit" << BSON("a" << BSON("or" << 0b0110)));
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(BSON("a" << 0b0101));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(BSON("a" << 0b0111), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0111)), logDoc);
+}
+
+TEST(BitNodeTest, ApplyAndLogSimpleDocumentXor) {
+ auto update = BSON("$bit" << BSON("a" << BSON("xor" << 0b0110)));
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(BSON("a" << 0b0101));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(BSON("a" << 0b0011), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0011)), logDoc);
+}
+
+TEST(BitNodeTest, ApplyShouldReportNoOp) {
+ auto update = BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1))));
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(BSON("a" << 1));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(BSON("a" << static_cast<int>(1)), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(fromjson("{}"), logDoc);
+}
+
+TEST(BitNodeTest, ApplyMultipleBitOps) {
+ // End-of-line comments help clang-format break up this line more readably.
+ auto update = BSON("$bit" << BSON("a" << BSON("and" << 0b1111000011110000 //
+ << //
+ "or" << 0b1100110011001100 //
+ << //
+ "xor" << 0b1010101010101010)));
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(BSON("a" << 0b1111111100000000));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(BSON("a" << 0b0101011001100110), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b0101011001100110)), logDoc);
+}
+
+TEST(BitNodeTest, ApplyRepeatedBitOps) {
+ auto update = BSON("$bit" << BSON("a" << BSON("xor" << 0b11001100 << "xor" << 0b10101010)));
+ const CollatorInterface* collator = nullptr;
+ BitNode node;
+ ASSERT_OK(node.init(update["$bit"]["a"], collator));
+
+ Document doc(BSON("a" << 0b11110000));
+ FieldRef pathToCreate("");
+ FieldRef pathTaken("a");
+ StringData matchedField;
+ auto fromReplication = false;
+ auto validateForStorage = true;
+ FieldRefSet immutablePaths;
+ const UpdateIndexData* indexData = nullptr;
+ 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_EQUALS(BSON("a" << 0b10010110), doc);
+ ASSERT_TRUE(doc.isInPlaceModeEnabled());
+ ASSERT_EQUALS(BSON("$set" << BSON("a" << 0b10010110)), logDoc);
+}
+
+} // namespace
diff --git a/src/mongo/db/update/modifier_table.cpp b/src/mongo/db/update/modifier_table.cpp
index 9c757b51db1..73ea732c4aa 100644
--- a/src/mongo/db/update/modifier_table.cpp
+++ b/src/mongo/db/update/modifier_table.cpp
@@ -48,6 +48,7 @@
#include "mongo/db/ops/modifier_unset.h"
#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/conflict_placeholder_node.h"
#include "mongo/db/update/pop_node.h"
#include "mongo/db/update/rename_node.h"
@@ -187,6 +188,8 @@ std::unique_ptr<UpdateLeafNode> makeUpdateLeafNode(ModifierType modType) {
switch (modType) {
case MOD_ADD_TO_SET:
return stdx::make_unique<AddToSetNode>();
+ case MOD_BIT:
+ return stdx::make_unique<BitNode>();
case MOD_CONFLICT_PLACEHOLDER:
return stdx::make_unique<ConflictPlaceholderNode>();
case MOD_INC:
diff --git a/src/mongo/db/update/set_node.cpp b/src/mongo/db/update/set_node.cpp
index 8b3e662ceb5..8980247c8d6 100644
--- a/src/mongo/db/update/set_node.cpp
+++ b/src/mongo/db/update/set_node.cpp
@@ -48,6 +48,7 @@ void SetNode::updateExistingElement(mutablebson::Element* element, bool* noop) c
if (element->getValue().binaryEqualValues(_val)) {
*noop = true;
} else {
+ *noop = false;
invariantOK(element->setValueBSONElement(_val));
}
}