summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Morrow <acm@10gen.com>2013-05-06 17:22:08 -0400
committerAndrew Morrow <acm@10gen.com>2013-05-07 17:18:30 -0400
commit3049ab5ae2b3e672f17a001be0f7cdf7e599a9fb (patch)
treeca6558d87e37bfd54182827460b2b8ed93b19da4 /src
parent6932776c728f65860c88813626efe94513fbf83b (diff)
downloadmongo-3049ab5ae2b3e672f17a001be0f7cdf7e599a9fb.tar.gz
SERVER-7174 Implement $inc on new update framework
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/ops/SConscript10
-rw-r--r--src/mongo/db/ops/modifier_inc.cpp266
-rw-r--r--src/mongo/db/ops/modifier_inc.h73
-rw-r--r--src/mongo/db/ops/modifier_inc_test.cpp401
4 files changed, 750 insertions, 0 deletions
diff --git a/src/mongo/db/ops/SConscript b/src/mongo/db/ops/SConscript
index 4c35518a886..ccd175ff2c6 100644
--- a/src/mongo/db/ops/SConscript
+++ b/src/mongo/db/ops/SConscript
@@ -39,6 +39,7 @@ env.CppUnitTest(
env.StaticLibrary(
target='update',
source=[
+ 'modifier_inc.cpp',
'modifier_set.cpp',
'modifier_unset.cpp',
],
@@ -48,6 +49,15 @@ env.StaticLibrary(
)
env.CppUnitTest(
+ target='modifier_inc_test',
+ source='modifier_inc_test.cpp',
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/mutable_bson_test_utils',
+ 'update',
+ ],
+)
+
+env.CppUnitTest(
target='modifier_set_test',
source='modifier_set_test.cpp',
LIBDEPS=[
diff --git a/src/mongo/db/ops/modifier_inc.cpp b/src/mongo/db/ops/modifier_inc.cpp
new file mode 100644
index 00000000000..00a773f6329
--- /dev/null
+++ b/src/mongo/db/ops/modifier_inc.cpp
@@ -0,0 +1,266 @@
+/**
+ * Copyright (C) 2013 10gen 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/>.
+ */
+
+#include "mongo/db/ops/modifier_inc.h"
+
+#include "mongo/base/error_codes.h"
+#include "mongo/bson/mutable/document.h"
+#include "mongo/db/ops/field_checker.h"
+#include "mongo/db/ops/path_support.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+
+ using mongoutils::str::stream;
+
+ struct ModifierInc::PreparedState {
+
+ PreparedState(mutablebson::Document& doc)
+ : doc(doc)
+ , idxFound(0)
+ , elemFound(doc.end())
+ , boundDollar("")
+ , newValue()
+ , inPlace(false)
+ , noOp(false) {
+ }
+
+ // Document that is going to be changed.
+ mutablebson::Document& doc;
+
+ // Index in _fieldRef for which an Element exist in the document.
+ int32_t idxFound;
+
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mutablebson::Element elemFound;
+
+ // Value to bind to a $-positional field, if one is provided.
+ std::string boundDollar;
+
+ // Value to be applied
+ SafeNum newValue;
+
+ // This $inc is in-place?
+ bool inPlace;
+
+ // This $inc is a no-op?
+ bool noOp;
+ };
+
+ ModifierInc::ModifierInc()
+ : ModifierInterface ()
+ , _fieldRef()
+ , _posDollar(0)
+ , _val() {
+ }
+
+ ModifierInc::~ModifierInc() {
+ }
+
+ Status ModifierInc::init(const BSONElement& modExpr) {
+
+ //
+ // field name analysis
+ //
+
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (! status.isOK()) {
+ return status;
+ }
+
+ // If a $-positional operator was used, get the index in which it occurred.
+ fieldchecker::isPositional(_fieldRef, &_posDollar);
+
+ //
+ // value analysis
+ //
+
+ if (!modExpr.isNumber()) {
+ // TODO: Context for mod error messages would be helpful
+ // include mod code, etc.
+ return Status(ErrorCodes::BadValue,
+ "Cannot increment with non-numeric argument");
+ }
+
+ _val = modExpr;
+ dassert(_val.isValid());
+
+ return Status::OK();
+ }
+
+ Status ModifierInc::prepare(mutablebson::Element root,
+ const StringData& matchedField,
+ ExecInfo* execInfo) {
+
+ _preparedState.reset(new PreparedState(root.getDocument()));
+
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
+ return Status(ErrorCodes::BadValue, "matched field not provided");
+ }
+ _preparedState->boundDollar = matchedField.toString();
+ _fieldRef.setPart(_posDollar, _preparedState->boundDollar);
+ }
+
+ // Locate the field name in 'root'. Note that we may not have all the parts in the path
+ // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
+ // apply is a noOp or whether is can be in place. The remaining path, if missing, will
+ // be created during the apply.
+ Status status = pathsupport::findLongestPrefix(_fieldRef,
+ root,
+ &_preparedState->idxFound,
+ &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ }
+ else if (!status.isOK()) {
+ return status;
+ }
+
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
+
+ // Capture the value we are going to write. At this point, there may not be a value
+ // against which to operate, so the result will be simply _val.
+ _preparedState->newValue = _val;
+
+ //
+ // in-place and no-op logic
+ //
+ // If the field path is not fully present, then this mod cannot be in place, nor is a
+ // noOp.
+ if (!_preparedState->elemFound.ok() ||
+ _preparedState->idxFound < static_cast<int32_t>(_fieldRef.numParts() - 1)) {
+ return Status::OK();
+ }
+
+ // If the value being $inc'ed is the same as the one already in the doc, than this is a
+ // noOp.
+ if (!_preparedState->elemFound.isNumeric())
+ return Status(ErrorCodes::BadValue,
+ "invalid attempt to increment a non-numeric field");
+
+ const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
+
+ // TODO: Is it possible for the current value to be EOO?
+
+ // Update newValue w.r.t to the current value of the found element.
+ _preparedState->newValue += currentValue;
+
+ // If the result of the addition is invalid, we must return an error.
+ if (!_preparedState->newValue.isValid())
+ return Status(ErrorCodes::BadValue,
+ "Failed to increment current value");
+
+ // If the values are identical (same type, same value), then this is a no-op, and
+ // therefore in-place as well.
+ if (_preparedState->newValue.isIdentical(currentValue)) {
+ _preparedState->noOp = execInfo->noOp = true;
+ _preparedState->inPlace = execInfo->inPlace = true;
+ return Status::OK();
+ }
+
+ // If the types are the same, this can be done in place.
+ //
+ // TODO: Potentially, cases where $inc results in a mixed type of the same size could
+ // be in-place as well, but we don't currently handle them.
+ if (_preparedState->newValue.type() == currentValue.type()) {
+ _preparedState->inPlace = execInfo->inPlace = true;
+ }
+
+ return Status::OK();
+ }
+
+ Status ModifierInc::apply() const {
+ dassert(_preparedState->noOp == false);
+
+ // If there's no need to create any further field part, the $inc is simply a value
+ // assignment.
+ if (_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == static_cast<int32_t>(_fieldRef.numParts() - 1)) {
+ return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
+ }
+
+ dassert(_preparedState->inPlace == false);
+
+ //
+ // Complete document path logic
+ //
+
+ // Creates the final element that's going to be $set in 'doc'.
+ mutablebson::Document& doc = _preparedState->doc;
+ StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
+ mutablebson::Element elemToSet = doc.makeElementSafeNum(lastPart, _preparedState->newValue);
+ if (!elemToSet.ok()) {
+ return Status(ErrorCodes::InternalError, "can't create new element");
+ }
+
+ // Now, we can be in two cases here, as far as attaching the element being set goes:
+ // (a) none of the parts in the element's path exist, or (b) some parts of the path
+ // exist but not all.
+ if (!_preparedState->elemFound.ok()) {
+ _preparedState->elemFound = doc.root();
+ _preparedState->idxFound = 0;
+ }
+ else {
+ _preparedState->idxFound++;
+ }
+
+ // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
+ return pathsupport::createPathAt(_fieldRef,
+ _preparedState->idxFound,
+ _preparedState->elemFound,
+ elemToSet);
+ }
+
+ Status ModifierInc::log(mutablebson::Element logRoot) const {
+
+ // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
+ // We start by creating the {$set: ...} Element.
+ mutablebson::Document& doc = logRoot.getDocument();
+ mutablebson::Element setElement = doc.makeElementObject("$set");
+ if (!setElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot append log entry for $set mod");
+ }
+
+ // Then we create the {<fieldname>: <value>} Element.
+ mutablebson::Element logElement = doc.makeElementSafeNum(
+ _fieldRef.dottedField(),
+ _preparedState->newValue);
+
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot append details for $set mod");
+ }
+
+ // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} one.
+ Status status = setElement.pushBack(logElement);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // And attach the result under the 'logRoot' Element provided.
+ return logRoot.pushBack(setElement);
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_inc.h b/src/mongo/db/ops/modifier_inc.h
new file mode 100644
index 00000000000..5dc9f8fae95
--- /dev/null
+++ b/src/mongo/db/ops/modifier_inc.h
@@ -0,0 +1,73 @@
+/**
+ * Copyright (C) 2013 10gen 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/>.
+ */
+
+#pragma once
+
+#include <boost/scoped_ptr.hpp>
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/bson/mutable/element.h"
+#include "mongo/db/field_ref.h"
+#include "mongo/db/ops/modifier_interface.h"
+
+namespace mongo {
+
+ class ModifierInc : public ModifierInterface {
+ MONGO_DISALLOW_COPYING(ModifierInc);
+
+ public:
+
+ ModifierInc();
+ virtual ~ModifierInc();
+
+ /**
+ * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $inc mod such as
+ * {$inc: {<fieldname: <value>}}. init() extracts the field name and the value to be
+ * assigned to it from 'modExpr'. It returns OK if successful or a status describing
+ * the error.
+ */
+ virtual Status init(const BSONElement& modExpr);
+
+ /** Evaluates the validity of applying $inc to the identified node, and computes
+ * effects, handling upcasting and overflow as necessary.
+ */
+ virtual Status prepare(mutablebson::Element root,
+ const StringData& matchedField,
+ ExecInfo* execInfo);
+
+ /** Updates the node passed in prepare with the results of the $inc */
+ virtual Status apply() const;
+
+ /** Converts the result of the $inc into an equivalent $set under logRoot */
+ virtual Status log(mutablebson::Element logRoot) const;
+
+ private:
+
+ // Access to each component of fieldName that's the target of this mod.
+ FieldRef _fieldRef;
+
+ // 0 or index for $-positional in _fieldRef.
+ size_t _posDollar;
+
+ // Element of the $set expression.
+ SafeNum _val;
+
+ struct PreparedState;
+ scoped_ptr<PreparedState> _preparedState;
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_inc_test.cpp b/src/mongo/db/ops/modifier_inc_test.cpp
new file mode 100644
index 00000000000..ad78567cbdf
--- /dev/null
+++ b/src/mongo/db/ops/modifier_inc_test.cpp
@@ -0,0 +1,401 @@
+/**
+ * Copyright (C) 2013 10gen 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/>.
+ */
+
+
+#include "mongo/db/ops/modifier_inc.h"
+
+#include "mongo/base/status.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/mutable/algorithm.h"
+#include "mongo/bson/mutable/document.h"
+#include "mongo/bson/mutable/mutable_bson_test_utils.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/json.h"
+#include "mongo/platform/cstdint.h"
+#include "mongo/unittest/unittest.h"
+
+namespace {
+
+ using mongo::BSONObj;
+ using mongo::ModifierInc;
+ using mongo::ModifierInterface;
+ using mongo::NumberInt;
+ using mongo::Status;
+ using mongo::StringData;
+ using mongo::fromjson;
+ using mongo::mutablebson::checkDoc;
+ using mongo::mutablebson::ConstElement;
+ using mongo::mutablebson::Document;
+ using mongo::mutablebson::Element;
+ using mongo::mutablebson::countChildren;
+
+ /** Helper to build and manipulate a $inc mod. */
+ class Mod {
+ public:
+ Mod() : _mod() {}
+
+ explicit Mod(BSONObj modObj)
+ : _modObj(modObj)
+ , _mod() {
+ ASSERT_OK(_mod.init(_modObj["$inc"].embeddedObject().firstElement()));
+ }
+
+ Status prepare(Element root,
+ const StringData& matchedField,
+ ModifierInterface::ExecInfo* execInfo) {
+ return _mod.prepare(root, matchedField, execInfo);
+ }
+
+ Status apply() const {
+ return _mod.apply();
+ }
+
+ Status log(Element logRoot) const {
+ return _mod.log(logRoot);
+ }
+
+ ModifierInc& mod() { return _mod; }
+
+ private:
+ BSONObj _modObj;
+ ModifierInc _mod;
+ };
+
+ TEST(Init, FailToInitWithInvalidValue) {
+ BSONObj modObj;
+ ModifierInc mod;
+
+ // String is an invalid increment argument
+ modObj = fromjson("{ $inc : { a : '' } }");
+ ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement()));
+
+ // Object is an invalid increment argument
+ modObj = fromjson("{ $inc : { a : {} } }");
+ ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement()));
+
+ // Array is an invalid increment argument
+ modObj = fromjson("{ $inc : { a : [] } }");
+ ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement()));
+ }
+
+ TEST(Init, InitParsesNumberInt) {
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(1))));
+ }
+
+ TEST(Init, InitParsesNumberLong) {
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(1))));
+ }
+
+ TEST(Init, InitParsesNumberDouble) {
+ Mod incMod(BSON("$inc" << BSON("a" << 1.0)));
+ }
+
+ TEST(SimpleMod, PrepareSimpleOK) {
+ Document doc(fromjson("{ a : 1 }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+
+ ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
+ ASSERT_TRUE(execInfo.inPlace);
+ ASSERT_FALSE(execInfo.noOp);
+ }
+
+ TEST(SimpleMod, PrepareSimpleNonNumericObject) {
+ Document doc(fromjson("{ a : {} }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ }
+
+ TEST(SimpleMod, PrepareSimpleNonNumericArray) {
+
+ Document doc(fromjson("{ a : [] }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ }
+
+ TEST(SimpleMod, PrepareSimpleNonNumericString) {
+ Document doc(fromjson("{ a : '' }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_NOT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ }
+
+ TEST(SimpleMod, ApplyAndLogEmptyDocument) {
+ Document doc(fromjson("{}"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.inPlace);
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : 1 }")));
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { a : 1 } }")));
+ }
+
+ TEST(SimpleMod, ApplyAndLogSimpleDocument) {
+ Document doc(fromjson("{ a : 2 }"));
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.inPlace);
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : 3 }")));
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { a : 3 } }")));
+ }
+
+ TEST(DottedMod, ApplyAndLogSimpleDocument) {
+ Document doc(fromjson("{ a : { b : 2 } }"));
+ Mod incMod(fromjson("{ $inc: { 'a.b' : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.inPlace);
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : { b : 3 } }")));
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { 'a.b' : 3 } }")));
+ }
+
+ TEST(InPlace, IntToInt) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(1))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+ }
+
+ TEST(InPlace, LongToLong) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(1))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+ }
+
+ TEST(InPlace, DoubleToDouble) {
+ Document doc(BSON("a" << 1.0));
+ Mod incMod(BSON("$inc" << BSON("a" << 1.0 )));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+ }
+
+ TEST(NoOp, Int) {
+ Document doc(BSON("a" << static_cast<int>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<int>(0))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+ }
+
+ TEST(NoOp, Long) {
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(0))));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+ }
+
+ TEST(NoOp, Double) {
+ Document doc(BSON("a" << 1.0));
+ Mod incMod(BSON("$inc" << BSON("a" << 0.0)));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+ }
+
+ TEST(Upcasting, UpcastIntToLong) {
+ // Checks that $inc : NumberLong(0) turns a NumberInt into a NumberLong and logs it
+ // correctly.
+ Document doc(BSON("a" << static_cast<int>(1)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(BSON("$inc" << BSON("a" << static_cast<long long>(0))));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_FALSE(execInfo.inPlace);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : 1 }")));
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { a : 1 } }")));
+ ASSERT_EQUALS(mongo::NumberLong, logDoc.root()["$set"]["a"].getType());
+ }
+
+ TEST(Upcasting, UpcastIntToDouble) {
+ // Checks that $inc : 0.0 turns a NumberInt into a NumberDouble and logs it
+ // correctly.
+ Document doc(BSON("a" << static_cast<int>(1)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 0.0 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_FALSE(execInfo.inPlace);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : 1.0 }")));
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { a : 1.0 } }")));
+ ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
+ }
+
+ TEST(Upcasting, UpcastLongToDouble) {
+ // Checks that $inc : 0.0 turns a NumberLong into a NumberDouble and logs it
+ // correctly.
+ Document doc(BSON("a" << static_cast<long long>(1)));
+ ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 0.0 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_FALSE(execInfo.inPlace);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : 1.0 }")));
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { a : 1.0 } }")));
+ ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
+ }
+
+ TEST(Upcasting, DoublesStayDoubles) {
+ // Checks that $inc : 0 doesn't change a NumberDouble away from double
+ Document doc(fromjson("{ a : 1.0 }"));
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 1 } }"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_TRUE(execInfo.inPlace);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, fromjson("{ a : 2.0 }")));
+ ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
+
+ Document logDoc;
+ ASSERT_OK(incMod.log(logDoc.root()));
+ ASSERT_TRUE(checkDoc(logDoc, fromjson("{ $set : { a : 2.0 } }")));
+ ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
+ }
+
+ // The only interesting overflow cases are int->long via increment: we never overflow to
+ // double, and we never decrease precision on decrement.
+
+ TEST(Spilling, OverflowIntToLong) {
+ const int initial_value = std::numeric_limits<int32_t>::max();
+
+ Document doc(BSON("a" << static_cast<int>(initial_value)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : 1 } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_FALSE(execInfo.inPlace);
+
+ const long long target_value = static_cast<long long>(initial_value) + 1;
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, BSON("a" << target_value)));
+ }
+
+ TEST(Spilling, UnderflowIntToLong) {
+ const int initial_value = std::numeric_limits<int32_t>::min();
+
+ Document doc(BSON("a" << static_cast<int>(initial_value)));
+ ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
+
+ Mod incMod(fromjson("{ $inc : { a : -1 } }"));
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
+ ASSERT_FALSE(execInfo.noOp);
+ ASSERT_FALSE(execInfo.inPlace);
+
+ const long long target_value = static_cast<long long>(initial_value) - 1;
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc, BSON("a" << target_value)));
+ }
+
+ TEST(Lifecycle, IncModCanBeReused) {
+ Document doc1(fromjson("{ a : 1 }"));
+ Document doc2(fromjson("{ a : 1 }"));
+
+ Mod incMod(fromjson("{ $inc: { a : 1 }}"));
+
+ ModifierInterface::ExecInfo execInfo;
+ ASSERT_OK(incMod.prepare(doc1.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.inPlace);
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc1, fromjson("{ a : 2 }")));
+
+ ASSERT_OK(incMod.prepare(doc2.root(), "", &execInfo));
+ ASSERT_TRUE(execInfo.inPlace);
+ ASSERT_FALSE(execInfo.noOp);
+
+ ASSERT_OK(incMod.apply());
+ ASSERT_TRUE(checkDoc(doc2, fromjson("{ a : 2 }")));
+ }
+
+} // namespace