summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/update_arrayFilters.js23
-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
8 files changed, 687 insertions, 16 deletions
diff --git a/jstests/core/update_arrayFilters.js b/jstests/core/update_arrayFilters.js
index 1635baffde5..ccd636c9809 100644
--- a/jstests/core/update_arrayFilters.js
+++ b/jstests/core/update_arrayFilters.js
@@ -497,24 +497,17 @@
"update failed for a reason other than using array updates with $push");
// $bit.
- // TODO SERVER-28765: $bit should use the new update implementation.
coll.drop();
+ assert.writeOK(coll.insert({_id: 0, a: [NumberInt(0), NumberInt(2)]}));
if (db.getMongo().writeMode() === "commands") {
- res = coll.update(
- {_id: 0}, {$bit: {"a.$[i]": {or: NumberInt(10)}}}, {arrayFilters: [{i: 0}]});
- assert.writeErrorWithCode(res, ErrorCodes.InvalidOptions);
- assert.neq(
- -1,
- res.getWriteError().errmsg.indexOf("Cannot use array filters with modifier $bit"),
- "update failed for a reason other than using array filters with $bit");
+ assert.writeOK(coll.update(
+ {_id: 0}, {$bit: {"a.$[i]": {or: NumberInt(10)}}}, {arrayFilters: [{i: 0}]}));
+ assert.eq({_id: 0, a: [NumberInt(10), NumberInt(2)]}, coll.findOne());
}
- assert.writeOK(coll.insert({_id: 0, a: [0]}));
- res = coll.update({_id: 0}, {$bit: {"a.$[]": {or: NumberInt(10)}}});
- assert.writeErrorWithCode(res, 16837);
- assert.neq(-1,
- res.getWriteError().errmsg.indexOf(
- "cannot use the part (a of a.$[]) to traverse the element ({a: [ 0.0 ]})"),
- "update failed for a reason other than using array updates with $bit");
+ assert.writeOK(coll.remove({}));
+ assert.writeOK(coll.insert({_id: 0, a: [NumberInt(0), NumberInt(2)]}));
+ assert.writeOK(coll.update({_id: 0}, {$bit: {"a.$[]": {or: NumberInt(10)}}}));
+ assert.eq({_id: 0, a: [NumberInt(10), NumberInt(10)]}, coll.findOne());
//
// Multi update tests.
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));
}
}