diff options
-rw-r--r-- | jstests/core/update_arrayFilters.js | 23 | ||||
-rw-r--r-- | src/mongo/db/update/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/update/arithmetic_node.h | 2 | ||||
-rw-r--r-- | src/mongo/db/update/bit_node.cpp | 134 | ||||
-rw-r--r-- | src/mongo/db/update/bit_node.h | 67 | ||||
-rw-r--r-- | src/mongo/db/update/bit_node_test.cpp | 471 | ||||
-rw-r--r-- | src/mongo/db/update/modifier_table.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/update/set_node.cpp | 1 |
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)); } } |