summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops')
-rw-r--r--src/mongo/db/ops/SConscript55
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.cpp390
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.h86
-rw-r--r--src/mongo/db/ops/modifier_add_to_set_test.cpp482
-rw-r--r--src/mongo/db/ops/modifier_bit.cpp294
-rw-r--r--src/mongo/db/ops/modifier_bit.h97
-rw-r--r--src/mongo/db/ops/modifier_bit_test.cpp798
-rw-r--r--src/mongo/db/ops/modifier_compare.cpp185
-rw-r--r--src/mongo/db/ops/modifier_compare.h113
-rw-r--r--src/mongo/db/ops/modifier_compare_test.cpp387
-rw-r--r--src/mongo/db/ops/modifier_current_date.cpp271
-rw-r--r--src/mongo/db/ops/modifier_current_date.h86
-rw-r--r--src/mongo/db/ops/modifier_current_date_test.cpp429
-rw-r--r--src/mongo/db/ops/modifier_inc.cpp283
-rw-r--r--src/mongo/db/ops/modifier_inc.h95
-rw-r--r--src/mongo/db/ops/modifier_inc_test.cpp673
-rw-r--r--src/mongo/db/ops/modifier_interface.h223
-rw-r--r--src/mongo/db/ops/modifier_pop.cpp201
-rw-r--r--src/mongo/db/ops/modifier_pop.h85
-rw-r--r--src/mongo/db/ops/modifier_pop_test.cpp324
-rw-r--r--src/mongo/db/ops/modifier_pull.cpp293
-rw-r--r--src/mongo/db/ops/modifier_pull.h91
-rw-r--r--src/mongo/db/ops/modifier_pull_all.cpp243
-rw-r--r--src/mongo/db/ops/modifier_pull_all.h88
-rw-r--r--src/mongo/db/ops/modifier_pull_all_test.cpp285
-rw-r--r--src/mongo/db/ops/modifier_pull_test.cpp776
-rw-r--r--src/mongo/db/ops/modifier_push.cpp660
-rw-r--r--src/mongo/db/ops/modifier_push.h127
-rw-r--r--src/mongo/db/ops/modifier_push_test.cpp1544
-rw-r--r--src/mongo/db/ops/modifier_rename.cpp304
-rw-r--r--src/mongo/db/ops/modifier_rename.h95
-rw-r--r--src/mongo/db/ops/modifier_rename_test.cpp468
-rw-r--r--src/mongo/db/ops/modifier_set.cpp276
-rw-r--r--src/mongo/db/ops/modifier_set.h109
-rw-r--r--src/mongo/db/ops/modifier_set_test.cpp847
-rw-r--r--src/mongo/db/ops/modifier_unset.cpp168
-rw-r--r--src/mongo/db/ops/modifier_unset.h99
-rw-r--r--src/mongo/db/ops/modifier_unset_test.cpp456
-rw-r--r--src/mongo/db/ops/parsed_update.cpp16
-rw-r--r--src/mongo/db/ops/update.cpp9
40 files changed, 5 insertions, 12506 deletions
diff --git a/src/mongo/db/ops/SConscript b/src/mongo/db/ops/SConscript
index c0ced70109d..aeb6b0d5980 100644
--- a/src/mongo/db/ops/SConscript
+++ b/src/mongo/db/ops/SConscript
@@ -20,61 +20,6 @@ env.Library(
],
)
-
-
-env.Library(
- target='update',
- source=[
- 'modifier_add_to_set.cpp',
- 'modifier_bit.cpp',
- 'modifier_compare.cpp',
- 'modifier_current_date.cpp',
- 'modifier_inc.cpp',
- 'modifier_pop.cpp',
- 'modifier_pull.cpp',
- 'modifier_pull_all.cpp',
- 'modifier_push.cpp',
- 'modifier_rename.cpp',
- 'modifier_set.cpp',
- 'modifier_unset.cpp',
- ],
- LIBDEPS=[
- '$BUILD_DIR/mongo/base',
- '$BUILD_DIR/mongo/db/service_context',
- '$BUILD_DIR/mongo/db/logical_clock',
- '$BUILD_DIR/mongo/db/logical_time',
- '$BUILD_DIR/mongo/db/bson/dotted_path_support',
- '$BUILD_DIR/mongo/db/matcher/expressions',
- '$BUILD_DIR/mongo/db/update/update_common',
- ],
-)
-
-env.CppUnitTest(
- target='modifier_update_test',
- source=[
- 'modifier_add_to_set_test.cpp',
- 'modifier_bit_test.cpp',
- 'modifier_compare_test.cpp',
- 'modifier_current_date_test.cpp',
- 'modifier_inc_test.cpp',
- 'modifier_pop_test.cpp',
- 'modifier_pull_all_test.cpp',
- 'modifier_pull_test.cpp',
- 'modifier_push_test.cpp',
- 'modifier_rename_test.cpp',
- 'modifier_set_test.cpp',
- 'modifier_unset_test.cpp',
- ],
- LIBDEPS=[
- '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils',
- '$BUILD_DIR/mongo/db/query/collation/collator_interface_mock',
- '$BUILD_DIR/mongo/db/query/query_test_service_context',
- '$BUILD_DIR/mongo/db/service_context_noop_init',
- '$BUILD_DIR/mongo/db/logical_clock',
- 'update',
- ],
-)
-
env.Library(
target='write_ops_parsers',
source=[
diff --git a/src/mongo/db/ops/modifier_add_to_set.cpp b/src/mongo/db/ops/modifier_add_to_set.cpp
deleted file mode 100644
index dc68fe11e84..00000000000
--- a/src/mongo/db/ops/modifier_add_to_set.cpp
+++ /dev/null
@@ -1,390 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_add_to_set.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/db/query/collation/collator_interface.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-namespace {
-
-template <typename Ordering, typename Equality>
-void deduplicate(mb::Element parent, Ordering comp, Equality equal) {
- // First, build a vector of the children.
- std::vector<mb::Element> children;
- mb::Element current = parent.leftChild();
- while (current.ok()) {
- children.push_back(current);
- current = current.rightSibling();
- }
-
- // Then, sort the child vector with our comparator.
- std::sort(children.begin(), children.end(), comp);
-
- // Next, remove duplicates by walking the vector.
- std::vector<mb::Element>::iterator where = children.begin();
- const std::vector<mb::Element>::iterator end = children.end();
-
- while (where != end) {
- std::vector<mb::Element>::iterator next = where;
- ++next;
- while (next != end && equal(*where, *next)) {
- next->remove().transitional_ignore();
- ++next;
- }
- where = next;
- }
-}
-
-} // namespace
-
-struct ModifierAddToSet::PreparedState {
- PreparedState(mb::Document& doc)
- : doc(doc),
- idxFound(0),
- elemFound(doc.end()),
- addAll(false),
- elementsToAdd(),
- noOp(false) {}
-
- // Document that is going to be changed.
- mb::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mb::Element elemFound;
-
- // Are we adding all of the $each elements, or just a subset?
- bool addAll;
-
- // Values to be applied.
- std::vector<mb::Element> elementsToAdd;
-
- // True if this update is a no-op
- bool noOp;
-};
-
-ModifierAddToSet::ModifierAddToSet()
- : ModifierInterface(), _fieldRef(), _posDollar(0), _valDoc(), _val(_valDoc.end()) {}
-
-ModifierAddToSet::~ModifierAddToSet() {}
-
-Status ModifierAddToSet::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- // 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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- // TODO: The driver could potentially do this re-writing.
-
- // If the type of the value is 'Object', we might be dealing with a $each. See if that
- // is the case.
- if (modExpr.type() == mongo::Object) {
- BSONElement modExprObjPayload = modExpr.embeddedObject().firstElement();
- if (!modExprObjPayload.eoo() && StringData(modExprObjPayload.fieldName()) == "$each") {
- // It is a $each. Verify that the payload is an array as is required for $each,
- // set our flag, and store the array as our value.
- if (modExprObjPayload.type() != mongo::Array) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The argument to $each in $addToSet must "
- "be an array but it was of type "
- << typeName(modExprObjPayload.type()));
- }
-
- status = _valDoc.root().appendElement(modExprObjPayload);
- if (!status.isOK())
- return status;
-
- _val = _valDoc.root().leftChild();
- }
- }
-
- // If this wasn't an 'each', turn it into one. No need to sort or de-dup since we only
- // have one element.
- if (_val == _valDoc.end()) {
- mb::Element each = _valDoc.makeElementArray("$each");
-
- status = each.appendElement(modExpr);
- if (!status.isOK())
- return status;
-
- status = _valDoc.root().pushBack(each);
- if (!status.isOK())
- return status;
-
- _val = each;
- }
-
- setCollator(opts.expCtx->getCollator());
- return Status::OK();
-}
-
-void ModifierAddToSet::setCollator(const CollatorInterface* collator) {
- invariant(!_collator);
- _collator = collator;
- // Deduplicate _val (must be performed after collator is set to final value.)
- deduplicate(_val, mb::woLess(_collator, false), mb::woEqual(_collator, false));
-}
-
-Status ModifierAddToSet::prepare(mb::Element root, 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
- // 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;
-
- //
- // 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 < (_fieldRef.numParts() - 1)) {
- // If no target element exists, we will simply be creating a new array.
- _preparedState->addAll = true;
-
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
-
- return Status::OK();
- }
-
- // This operation only applies to arrays
- if (_preparedState->elemFound.getType() != mongo::Array) {
- mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
- return Status(ErrorCodes::BadValue,
- str::stream() << "Cannot apply $addToSet to a non-array field. Field named '"
- << _preparedState->elemFound.getFieldName()
- << "' has a non-array type "
- << typeName(_preparedState->elemFound.getType())
- << " in the document "
- << idElem.toString());
- }
-
- // If the array is empty, then we don't need to check anything: all of the values are
- // going to be added.
- if (!_preparedState->elemFound.hasChildren()) {
- _preparedState->addAll = true;
- return Status::OK();
- }
-
- // For each value in the $each clause, compare it against the values in the array. If
- // the element is not present, record it as one to add.
- mb::Element eachIter = _val.leftChild();
- while (eachIter.ok()) {
- mb::Element where = mb::findElement(_preparedState->elemFound.leftChild(),
- mb::woEqualTo(eachIter, _collator, false));
- if (!where.ok()) {
- // The element was not found. Record the element from $each as one to be added.
- _preparedState->elementsToAdd.push_back(eachIter);
- }
- eachIter = eachIter.rightSibling();
- }
-
- // If we didn't find any elements to add, then this is a no-op.
- if (_preparedState->elementsToAdd.empty()) {
- _preparedState->noOp = execInfo->noOp = true;
- }
-
- return Status::OK();
-}
-
-Status ModifierAddToSet::apply() const {
- dassert(_preparedState->noOp == false);
-
- // TODO: The contents of this block are lifted directly from $push.
-
- // If the array field is not there, create it as an array and attach it to the
- // document.
- if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // Creates the array element
- mb::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
- mb::Element baseArray = doc.makeElementArray(lastPart);
- if (!baseArray.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new base array");
- }
-
- // 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.
- Status status =
- pathsupport::createPathAt(
- _fieldRef, _preparedState->idxFound, _preparedState->elemFound, baseArray)
- .getStatus();
- if (!status.isOK()) {
- return status;
- }
-
- // Point to the base array just created. The subsequent code expects it to exist
- // already.
- _preparedState->elemFound = baseArray;
- }
-
- if (_preparedState->addAll) {
- // If we are adding all the values, we can just walk over _val;
-
- mb::Element where = _val.leftChild();
- while (where.ok()) {
- dassert(where.hasValue());
-
- mb::Element toAdd = _preparedState->doc.makeElement(where.getValue());
- Status status = _preparedState->elemFound.pushBack(toAdd);
- if (!status.isOK())
- return status;
-
- where = where.rightSibling();
- }
-
- } else {
- // Otherwise, we aren't adding all the values, and we need to add exactly those
- // elements that were found to be missing during our scan in prepare.
- std::vector<mb::Element>::const_iterator where = _preparedState->elementsToAdd.begin();
-
- const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToAdd.end();
-
- for (; where != end; ++where) {
- dassert(where->hasValue());
-
- mb::Element toAdd = _preparedState->doc.makeElement(where->getValue());
- Status status = _preparedState->elemFound.pushBack(toAdd);
- if (!status.isOK())
- return status;
- }
- }
-
- return Status::OK();
-}
-
-Status ModifierAddToSet::log(LogBuilder* logBuilder) const {
- // TODO: This is copied more or less identically from $push. As a result, it copies the
- // behavior in $push that relies on 'apply' having been called unless this is a no-op.
-
- // TODO We can log just a positional set in several cases. For now, let's just log the
- // full resulting array.
-
- // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
- // 'logRoot'. We start by creating the {$set: ...} Element.
- mb::Document& doc = logBuilder->getDocument();
-
- // Then we create the {<fieldname>:[]} Element, that is, an empty array.
- mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $addToSet mod");
- }
-
- // Fill up the empty array.
- mb::Element curr = _preparedState->elemFound.leftChild();
- while (curr.ok()) {
- dassert(curr.hasValue());
-
- // We need to copy each array entry from the resulting document to the log
- // document.
- mb::Element currCopy = doc.makeElementWithNewFieldName(StringData(), curr.getValue());
- if (!currCopy.ok()) {
- return Status(ErrorCodes::InternalError, "could create copy element");
- }
- Status status = logElement.pushBack(currCopy);
- if (!status.isOK()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Could not append entry for $addToSet oplog entry."
- << "Underlying cause: "
- << status.toString());
- }
- curr = curr.rightSibling();
- }
-
- return logBuilder->addToSets(logElement);
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_add_to_set.h b/src/mongo/db/ops/modifier_add_to_set.h
deleted file mode 100644
index dd5c27021a6..00000000000
--- a/src/mongo/db/ops/modifier_add_to_set.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * 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/>.
- *
- * 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/base/disallow_copying.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class CollatorInterface;
-class LogBuilder;
-
-class ModifierAddToSet : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierAddToSet);
-
-public:
- ModifierAddToSet();
- virtual ~ModifierAddToSet();
-
- /** Goes over the array item(s) that are going to be set- unioned and converts them
- * internally to a mutable bson. Both single and $each forms are supported. Returns OK
- * if the item(s) are valid otherwise returns a status describing the error.
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
-
- /** Decides which portion of the array items that are going to be set-unioned to root's
- * document and fills in 'execInfo' accordingly. Returns OK if the document has a
- * valid array to set-union to, othwise returns a status describing the error.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /** Updates the Element used in prepare with the effects of the $addToSet operation. */
- virtual Status apply() const;
-
- /** Converts the effects of this $addToSet into one or more equivalent $set operations. */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator);
-
-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;
-
- // Array of values to be set-union'ed onto target.
- mutablebson::Document _valDoc;
- mutablebson::Element _val;
-
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- const CollatorInterface* _collator = nullptr;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_add_to_set_test.cpp b/src/mongo/db/ops/modifier_add_to_set_test.cpp
deleted file mode 100644
index e8a0b4944c2..00000000000
--- a/src/mongo/db/ops/modifier_add_to_set_test.cpp
+++ /dev/null
@@ -1,482 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_add_to_set.h"
-
-#include <cstdint>
-
-#include "mongo/base/string_data.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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::CollatorInterfaceMock;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::ModifierAddToSet;
-using mongo::ModifierInterface;
-using mongo::Status;
-using mongo::StringData;
-using mongo::fromjson;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate a $addToSet mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj,
- ModifierInterface::Options options =
- ModifierInterface::Options::normal(new ExpressionContextForTest()))
- : _modObj(modObj), _mod() {
- ASSERT_OK(_mod.init(_modObj["$addToSet"].embeddedObject().firstElement(), options));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierAddToSet& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierAddToSet _mod;
-};
-
-TEST(Init, ParsesSimple) {
- Mod(fromjson("{ $addToSet : { a : 1 } }"));
- Mod(fromjson("{ $addToSet : { a : 'foo' } }"));
- Mod(fromjson("{ $addToSet : { a : {} } }"));
- Mod(fromjson("{ $addToSet : { a : { x : 1 } } }"));
- Mod(fromjson("{ $addToSet : { a : [] } }"));
- Mod(fromjson("{ $addToSet : { a : [1, 2] } } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : 1 } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : 'foo' } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : {} } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : { x : 1} } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : [] } }"));
- Mod(fromjson("{ $addToSet : { 'a.b' : [1, 2] } } }"));
-}
-
-TEST(Init, ParsesEach) {
- Mod(fromjson("{ $addToSet : { a : { $each : [] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ 1 ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ 1, 2 ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ 1, 2, 1 ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ {} ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 } ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 }, { y : 2 } ] } } }"));
- Mod(fromjson("{ $addToSet : { a : { $each : [ { x : 1 }, { y : 2 }, { x : 1 } ] } } }"));
-}
-
-TEST(SimpleMod, PrepareOKTargetNotFound) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareOKTargetFound) {
- Document doc(fromjson("{ a : [ 1 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
-}
-
-TEST(SimpleMod, PrepareInvalidTargetNumber) {
- Document doc(fromjson("{ a : 1 }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, PrepareInvalidTarget) {
- Document doc(fromjson("{ a : {} }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, ApplyAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogEmptyArray) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1 ] } }"), logDoc);
-}
-
-TEST(SimpleEachMod, ApplyAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
-}
-
-TEST(SimpleEachMod, ApplyAndLogEmptyArray) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogPopulatedArray) {
- Document doc(fromjson("{ a : [ 'x' ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 'x', 1 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 'x', 1 ] } }"), logDoc);
-}
-
-TEST(SimpleEachMod, ApplyAndLogPopulatedArray) {
- Document doc(fromjson("{ a : [ 'x' ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 'x', 1, 2, 3 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 'x', 1, 2, 3 ] } }"), logDoc);
-}
-
-TEST(NoOp, AddOneExistingIsNoOp) {
- Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 1 } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
-}
-
-TEST(NoOp, AddSeveralExistingIsNoOp) {
- Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2] } } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
-}
-
-TEST(NoOp, AddAllExistingIsNoOp) {
- Document doc(fromjson("{ a : [ 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [1, 2, 3] } } }"));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 2, 3 ] } }"), logDoc);
-}
-
-TEST(Deduplication, ExistingDuplicatesArePreserved) {
- Document doc(fromjson("{ a : [ 1, 1, 2, 1, 2, 2 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : 3 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 1, 2, 1, 2, 2, 3] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 1, 2, 1, 2, 2, 3] } }"), logDoc);
-}
-
-TEST(Deduplication, NewDuplicatesAreElided) {
- Document doc(fromjson("{ a : [ 1, 1, 2, 1, 2, 2 ] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : [ 4, 1, 3, 2, 3, 1, 3, 3, 2, 4] } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 1, 2, 1, 2, 2, 4, 3] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [ 1, 1, 2, 1, 2, 2, 4, 3] } }"), logDoc);
-}
-
-TEST(Regressions, SERVER_12848) {
- // Proof that the mod works ok (the real issue was in validate).
-
- Document doc(fromjson("{ _id : 1, a : [ 1, [ ] ] }"));
- Mod mod(fromjson("{ $addToSet : { 'a.1' : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ _id : 1, a : [ 1, [ 1 ] ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { 'a.1' : [ 1 ] } }"), logDoc);
-}
-
-TEST(Deduplication, DeduplicationRespectsCollation) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString);
- Document doc(fromjson("{ a : ['bar'] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : ['FOO', 'foo'] } } }"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(mod.apply());
-
- ASSERT(doc.compareWithBSONObj(fromjson("{ a : ['bar', 'FOO'] }"), nullptr, false) == 0 ||
- doc.compareWithBSONObj(fromjson("{ a: ['bar', 'foo'] }"), nullptr, false) == 0);
-}
-
-TEST(Deduplication, ExistingDuplicatesArePreservedWithRespectToCollation) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString);
- Document doc(fromjson("{ a : ['bar', 'BAR'] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : ['FOO', 'foo'] } } }"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(doc, fromjson("{ a : ['bar', 'BAR', 'FOO'] }"));
-}
-
-TEST(Collation, AddToSetRespectsCollationFromModifierOptions) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- expCtx->setCollator(&collator);
- Document doc(fromjson("{ a : ['not'] }"));
- Mod mod(fromjson("{ $addToSet : { a : 'equal' } }"),
- ModifierInterface::Options::normal(expCtx));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(Collation, AddToSetRespectsCollationFromSetCollation) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- Document doc(fromjson("{ a : ['not'] }"));
- Mod mod(fromjson("{ $addToSet : { a : 'equal' } }"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(Collation, AddToSetWithEachRespectsCollation) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString);
- Document doc(fromjson("{ a : ['abc'] }"));
- Mod mod(fromjson("{ $addToSet : { a : { $each : ['ABC', 'bdc'] } } }"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(doc, fromjson("{ a : ['abc', 'bdc'] }"));
-}
-
-TEST(IndexedMod, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$addToSet: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$addToSet: {'a.0.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod mod(fromjson("{$addToSet: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-} // namespace
diff --git a/src/mongo/db/ops/modifier_bit.cpp b/src/mongo/db/ops/modifier_bit.cpp
deleted file mode 100644
index 75c096545cf..00000000000
--- a/src/mongo/db/ops/modifier_bit.cpp
+++ /dev/null
@@ -1,294 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_bit.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-struct ModifierBit::PreparedState {
- PreparedState(mutablebson::Document& doc)
- : doc(doc), idxFound(0), elemFound(doc.end()), noOp(false) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-
- // Value to be applied.
- SafeNum newValue;
-
- // True if this update is a no-op
- bool noOp;
-};
-
-ModifierBit::ModifierBit() : ModifierInterface(), _fieldRef(), _posDollar(0), _ops() {}
-
-ModifierBit::~ModifierBit() {}
-
-Status ModifierBit::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- // 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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- 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: #}}");
-
- if (modExpr.embeddedObject().isEmpty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "You must pass in at least one bitwise operation. "
- << "The format is: "
- "{$bit: {field: {and/or/xor: #}}");
- }
-
- BSONObjIterator opsIterator(modExpr.embeddedObject());
-
- while (opsIterator.more()) {
- BSONElement curOp = opsIterator.next();
-
- const StringData payloadFieldName = curOp.fieldName();
-
- SafeNumOp op = NULL;
-
- if (payloadFieldName == "and") {
- op = &SafeNum::bitAnd;
- } else if (payloadFieldName == "or") {
- op = &SafeNum::bitOr;
- } else if (payloadFieldName == "xor") {
- op = &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
- << "}");
-
- const OpEntry entry = {SafeNum(curOp), op};
- _ops.push_back(entry);
- }
-
- dassert(!_ops.empty());
-
- return Status::OK();
-}
-
-Status ModifierBit::prepare(mutablebson::Element root,
- 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
-
- // 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;
-
- //
- // 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 < (_fieldRef.numParts() - 1)) {
- // If no target element exists, the value we will write is the result of applying
- // the operation to a zero-initialized integer element.
- _preparedState->newValue = apply(SafeNum(static_cast<int32_t>(0)));
-
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
-
- return Status::OK();
- }
-
- if (!_preparedState->elemFound.isIntegral()) {
- mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
- return Status(ErrorCodes::BadValue,
- str::stream() << "Cannot apply $bit to a value of non-integral type."
- << idElem.toString()
- << " has the field "
- << _preparedState->elemFound.getFieldName()
- << " of non-integer type "
- << typeName(_preparedState->elemFound.getType()));
- }
-
- const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
-
- // Apply the op over the existing value and the mod value, and capture the result.
- _preparedState->newValue = apply(currentValue);
-
- if (!_preparedState->newValue.isValid()) {
- // TODO: Include list of ops, if that is easy, at some future point.
- return Status(ErrorCodes::BadValue,
- str::stream() << "Failed to apply $bit operations to current value: "
- << currentValue.debugString());
- }
- // If the values are identical (same type, same value), then this is a no-op.
- if (_preparedState->newValue.isIdentical(currentValue)) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
-
- return Status::OK();
-}
-
-Status ModifierBit::apply() const {
- dassert(_preparedState->noOp == false);
-
- // If there's no need to create any further field part, the $bit is simply a value
- // assignment.
- if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1)) {
- return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
- }
-
- //
- // 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)
- .getStatus();
-}
-
-Status ModifierBit::log(LogBuilder* logBuilder) const {
- mutablebson::Element logElement = logBuilder->getDocument().makeElementSafeNum(
- _fieldRef.dottedField(), _preparedState->newValue);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to $bit oplog entry: "
- << "set '"
- << _fieldRef.dottedField()
- << "' -> "
- << _preparedState->newValue.debugString());
- }
- return logBuilder->addToSets(logElement);
-}
-
-SafeNum ModifierBit::apply(SafeNum value) const {
- OpEntries::const_iterator where = _ops.begin();
- const OpEntries::const_iterator end = _ops.end();
- for (; where != end; ++where)
- value = (value.*(where->op))(where->val);
- return value;
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_bit.h b/src/mongo/db/ops/modifier_bit.h
deleted file mode 100644
index 7e91fee0920..00000000000
--- a/src/mongo/db/ops/modifier_bit.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-#include <vector>
-
-#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"
-#include "mongo/util/safe_num.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-class ModifierBit : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierBit);
-
-public:
- ModifierBit();
- virtual ~ModifierBit();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $bit mod such as
- * {$bit: {<field: { [and|or] : <value>}}. init() extracts the field name, the
- * operation subtype, 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, const Options& opts, bool* positional = NULL);
-
- /** Validates the potential application of the init'ed mod to the given Element and
- * configures the internal state of the mod as necessary.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /** Updates the Element used in prepare with the effects of the $bit operation */
- virtual Status apply() const;
-
- /** Converts the effects of this $bit into an equivalent $set */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-private:
- SafeNum apply(SafeNum value) const;
-
- // 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;
-
- // The operator on SafeNum that we will invoke.
- typedef SafeNum (SafeNum::*SafeNumOp)(const SafeNum&) const;
-
- struct OpEntry {
- SafeNum val;
- SafeNumOp op;
- };
-
- typedef std::vector<OpEntry> OpEntries;
-
- OpEntries _ops;
-
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_bit_test.cpp b/src/mongo/db/ops/modifier_bit_test.cpp
deleted file mode 100644
index 41633e22865..00000000000
--- a/src/mongo/db/ops/modifier_bit_test.cpp
+++ /dev/null
@@ -1,798 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_bit.h"
-
-#include <cstdint>
-
-#include "mongo/base/string_data.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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/platform/decimal128.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::Decimal128;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::ModifierBit;
-using mongo::ModifierInterface;
-using mongo::Status;
-using mongo::StringData;
-using mongo::fromjson;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate a $bit mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
- ASSERT_OK(_mod.init(_modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierBit& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierBit _mod;
-};
-
-
-TEST(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierBit mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- // Double is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : 0 } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // String is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : '' } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // Array is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : [] } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An empty document is an invalid $bit argument.
- modObj = fromjson("{$bit: {a: {}}}");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An object with value not in ('and', 'or') is an invalid $bit argument
- modObj = fromjson("{ $bit : { a : { foo : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // The argument to the sub-operator must be numeric
- modObj = fromjson("{ $bit : { a : { or : [] } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{ $bit : { a : { or : 'foo' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // The argument to the sub-operator must be integral
- modObj = fromjson("{ $bit : { a : { or : 1.0 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // The argument to the sub-operator must be integral
- modObj = fromjson("{ $bit : { a : { or : NumberDecimal(\"1.0\") } } }");
- ASSERT_NOT_OK(mod.init(modObj["$bit"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, ParsesAndInt) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))));
-}
-
-TEST(Init, ParsesOrInt) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
-}
-
-TEST(Init, ParsesXorInt) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
-}
-
-TEST(Init, ParsesAndLong) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-}
-
-TEST(Init, ParsesOrLong) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-}
-
-TEST(Init, ParsesXorLong) {
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
-}
-
-TEST(SimpleMod, PrepareOKTargetNotFound) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
-
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareOKTargetFound) {
- Document doc(fromjson("{ a : 1 }"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
-
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
-}
-
-TEST(SimpleMod, PrepareWithObjectShouldFail) {
- Document doc(fromjson("{ a : {} }"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, PrepareWithArrayShouldFail) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, PrepareWithStringShouldFail) {
- Document doc(fromjson("{ a : '' }"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, PrepareWithDoubleShouldFail) {
- Document doc(fromjson("{ a : 1.1 }"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, ApplyAndLogEmptyDocumentAnd) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { and : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogEmptyDocumentOr) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { or : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogEmptyDocumentXor) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $bit : { a : { xor : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogSimpleDocumentAnd) {
- Document doc(fromjson("{ a : 5 }"));
- Mod mod(fromjson("{ $bit : { a : { and : 6 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 4 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 4 } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogSimpleDocumentOr) {
- Document doc(fromjson("{ a : 5 }"));
- Mod mod(fromjson("{ $bit : { a : { or : 6 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 7 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 7 } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogSimpleDocumentXor) {
- Document doc(fromjson("{ a : 5 }"));
- Mod mod(fromjson("{ $bit : { a : { xor : 6 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 3 } }"), logDoc);
-}
-
-TEST(InPlace, IntToIntAndIsInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
-}
-
-TEST(InPlace, IntToIntOrIsInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
-}
-
-TEST(InPlace, IntToIntXorIsInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0))), logDoc);
-}
-
-TEST(InPlace, LongToLongAndIsInPlace) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(1))), logDoc);
-}
-
-TEST(InPlace, LongToLongOrIsInPlace) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(1))), logDoc);
-}
-
-TEST(InPlace, LongToLongXorIsInPlace) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0))), logDoc);
-}
-
-TEST(InPlace, IntToLongAndIsNotInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(InPlace, IntToLongOrIsNotInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(InPlace, IntToLongXorIsNotInPlace) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(NoOp, IntAnd) {
- Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(0xFFFFFFFFU)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
-}
-
-TEST(NoOp, IntOr) {
- Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(0x0U)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
-}
-
-TEST(NoOp, IntXor) {
- Document doc(BSON("a" << static_cast<int>(0xABCD1234U)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(0x0U)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(0xABCD1234U))), logDoc);
-}
-
-TEST(NoOp, LongAnd) {
- Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
- Mod mod(
- BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(0xFFFFFFFFFFFFFFFFULL)))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL))),
- logDoc);
-}
-
-TEST(NoOp, LongOr) {
- Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(0x0ULL)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL))),
- logDoc);
-}
-
-TEST(NoOp, LongXor) {
- Document doc(BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(0x0ULL)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<long long>(0xABCD1234EF981234ULL))),
- logDoc);
-}
-
-TEST(Upcasting, UpcastIntToLongAnd) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-}
-
-TEST(Upcasting, UpcastIntToLongOr) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<long long>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-}
-
-TEST(Upcasting, UpcastIntToLongXor) {
- Document doc(BSON("a" << static_cast<int>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<long long>(0)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-}
-
-TEST(Upcasting, LongsStayLongsAnd) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("and" << static_cast<int>(2)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-}
-
-TEST(Upcasting, LongsStayLongsOr) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(2)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-}
-
-TEST(Upcasting, LongsStayLongsXor) {
- Document doc(BSON("a" << static_cast<long long>(1)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("xor" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-}
-
-// The following tests are re-created from the previous $bit tests in updatetests.cpp. They
-// are probably redundant with the tests above in various ways.
-
-TEST(DbUpdateTests, BitRewriteExistingField) {
- Document doc(BSON("a" << static_cast<int>(0)));
- Mod mod(BSON("$bit" << BSON("a" << BSON("or" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << static_cast<int>(1)), doc);
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("a" << static_cast<int>(1))), logDoc);
-}
-
-TEST(DbUpdateTests, BitRewriteNonExistingField) {
- Document doc(BSON("a" << static_cast<int>(0)));
- Mod mod(BSON("$bit" << BSON("b" << BSON("or" << static_cast<int>(1)))));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << static_cast<int>(0) << "b" << static_cast<int>(1)), doc);
- ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("b" << static_cast<int>(1))), logDoc);
-}
-
-TEST(DbUpdateTests, Bit1_1) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod(BSON("$bit" << BSON("x" << BSON("and" << 2))));
- const BSONObj result(BSON("_id" << 1 << "x" << (3 & 2)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << (3 & 2))), logDoc);
-}
-
-TEST(DbUpdateTests, Bit1_2) {
- Document doc(BSON("_id" << 1 << "x" << 1));
- Mod mod(BSON("$bit" << BSON("x" << BSON("or" << 4))));
- const BSONObj result(BSON("_id" << 1 << "x" << (1 | 4)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << (1 | 4))), logDoc);
-}
-
-TEST(DbUpdateTests, Bit1_3) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod1(BSON("$bit" << BSON("x" << BSON("and" << 2))));
- Mod mod2(BSON("$bit" << BSON("x" << BSON("or" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 & 2) | 8)));
-
- ModifierInterface::ExecInfo execInfo1;
- ASSERT_OK(mod1.prepare(doc.root(), "", &execInfo1));
- if (!execInfo1.noOp)
- ASSERT_OK(mod1.apply());
-
- ModifierInterface::ExecInfo execInfo2;
- ASSERT_OK(mod2.prepare(doc.root(), "", &execInfo2));
- if (!execInfo2.noOp)
- ASSERT_OK(mod2.apply());
-
- ASSERT_EQUALS(result, doc);
-}
-
-TEST(DbUpdateTests, Bit1_3_Combined) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod(BSON("$bit" << BSON("x" << BSON("and" << 2 << "or" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 & 2) | 8)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 & 2) | 8))), logDoc);
-}
-
-TEST(DbUpdateTests, Bit1_4) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod1(BSON("$bit" << BSON("x" << BSON("or" << 2))));
- Mod mod2(BSON("$bit" << BSON("x" << BSON("and" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 | 2) & 8)));
-
- ModifierInterface::ExecInfo execInfo1;
- ASSERT_OK(mod1.prepare(doc.root(), "", &execInfo1));
- if (!execInfo1.noOp)
- ASSERT_OK(mod1.apply());
-
- ModifierInterface::ExecInfo execInfo2;
- ASSERT_OK(mod2.prepare(doc.root(), "", &execInfo2));
- if (!execInfo2.noOp)
- ASSERT_OK(mod2.apply());
-
- ASSERT_EQUALS(result, doc);
-}
-
-TEST(DbUpdateTests, Bit1_4_Combined) {
- Document doc(BSON("_id" << 1 << "x" << 3));
- Mod mod(BSON("$bit" << BSON("x" << BSON("or" << 2 << "and" << 8))));
- const BSONObj result(BSON("_id" << 1 << "x" << ((3 | 2) & 8)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- if (!execInfo.noOp)
- ASSERT_OK(mod.apply());
-
- ASSERT_EQUALS(result, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(BSON("$set" << BSON("x" << ((3 | 2) & 8))), logDoc);
-}
-
-TEST(IndexedMod, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$bit: {'a.1.c': {and: NumberInt(1)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: NumberInt(0)}]}"));
- Mod mod(fromjson("{$bit: {'a.0.c': {or: NumberInt(1)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod mod(fromjson("{$bit: {'a.1.c': {and: NumberInt(1)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-} // namespace
diff --git a/src/mongo/db/ops/modifier_compare.cpp b/src/mongo/db/ops/modifier_compare.cpp
deleted file mode 100644
index 0ea27b22575..00000000000
--- a/src/mongo/db/ops/modifier_compare.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_compare.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/query/collation/collator_interface.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace str = mongoutils::str;
-
-struct ModifierCompare::PreparedState {
- PreparedState(mutablebson::Document& targetDoc)
- : doc(targetDoc), idxFound(0), elemFound(doc.end()) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-};
-
-ModifierCompare::ModifierCompare(ModifierCompare::ModifierCompareMode mode)
- : _mode(mode), _pathReplacementPosition(0) {}
-
-ModifierCompare::~ModifierCompare() {}
-
-Status ModifierCompare::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- _updatePath.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_updatePath);
- if (!status.isOK()) {
- return status;
- }
-
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar =
- fieldchecker::isPositional(_updatePath, &_pathReplacementPosition, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _updatePath.dottedField()
- << "'");
- }
-
- // Store value for later.
- _val = modExpr;
- _collator = opts.expCtx->getCollator();
- return Status::OK();
-}
-
-Status ModifierCompare::prepare(mutablebson::Element root,
- 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 (_pathReplacementPosition) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _updatePath.dottedField());
- }
- _updatePath.setPart(_pathReplacementPosition, matchedField);
- }
-
- // 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(
- _updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
- // 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] = &_updatePath;
-
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
- if (!destExists) {
- execInfo->noOp = false;
-
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
- } else {
- const int compareVal =
- _preparedState->elemFound.compareWithBSONElement(_val, _collator, false);
- execInfo->noOp = (compareVal == 0) ||
- ((_mode == ModifierCompare::MAX) ? (compareVal > 0) : (compareVal < 0));
- }
-
- return Status::OK();
-}
-
-Status ModifierCompare::apply() const {
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
- // If there's no need to create any further field part, the $set is simply a value
- // assignment.
- if (destExists) {
- return _preparedState->elemFound.setValueBSONElement(_val);
- }
-
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
- // If the element exists and is the same type, then that is what we want to work with
- mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val);
- 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(
- _updatePath, _preparedState->idxFound, _preparedState->elemFound, elemToSet)
- .getStatus();
-}
-
-Status ModifierCompare::log(LogBuilder* logBuilder) const {
- return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(), _val);
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_compare.h b/src/mongo/db/ops/modifier_compare.h
deleted file mode 100644
index 44cfdf15c7e..00000000000
--- a/src/mongo/db/ops/modifier_compare.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class CollatorInterface;
-class LogBuilder;
-
-class ModifierCompare : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierCompare);
-
-public:
- enum ModifierCompareMode { MAX, MIN };
- explicit ModifierCompare(ModifierCompareMode mode = MAX);
-
- virtual ~ModifierCompare();
-
- //
- // Modifier interface implementation
- //
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
- * {$set: {<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, const Options& opts, bool* positional = NULL);
-
- /**
- * Looks up the field name in the sub-tree rooted at 'root', and binds, if necessary,
- * the '$' field part using the 'matchedfield' number. prepare() returns OK and
- * fills in 'execInfo' with information of whether this mod is a no-op on 'root' and
- * whether it is an in-place candidate. Otherwise, returns a status describing the
- * error.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /**
- * Applies the prepared mod over the element 'root' specified in the prepare()
- * call. Returns OK if successful or a status describing the error.
- */
- virtual Status apply() const;
-
- /**
- * Adds a log entry to logRoot corresponding to the operation applied here. Returns OK
- * if successful or a status describing the error.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator) {
- invariant(!_collator);
- _collator = collator;
- }
-
-private:
- // Compare mode: min/max
- const ModifierCompareMode _mode;
-
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _updatePath;
-
- // 0 or index for $-positional in _updatePath.
- size_t _pathReplacementPosition;
-
- // Element of the mod expression.
- BSONElement _val;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- const CollatorInterface* _collator = nullptr;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_compare_test.cpp b/src/mongo/db/ops/modifier_compare_test.cpp
deleted file mode 100644
index 583a952697c..00000000000
--- a/src/mongo/db/ops/modifier_compare_test.cpp
+++ /dev/null
@@ -1,387 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_compare.h"
-
-#include <cstdint>
-
-#include "mongo/base/string_data.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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::CollatorInterfaceMock;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::ModifierCompare;
-using mongo::ModifierInterface;
-using mongo::Status;
-using mongo::StringData;
-using mongo::fromjson;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-const char kModNameMin[] = "$min";
-const char kModNameMax[] = "$max";
-
-/** Helper to build and manipulate a $min/max mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj,
- ModifierInterface::Options options =
- ModifierInterface::Options::normal(new ExpressionContextForTest()))
- : _modObj(modObj),
- _mod((modObj.firstElement().fieldNameStringData() == "$min") ? ModifierCompare::MIN
- : ModifierCompare::MAX) {
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(modObj[modName].embeddedObject().firstElement(), options));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierCompare& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierCompare _mod;
-};
-
-TEST(Init, ValidValues) {
- BSONObj modObj;
- ModifierCompare mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- modObj = fromjson("{ $min : { a : 2 } }");
- ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{ $max : { a : 1 } }");
- ASSERT_OK(mod.init(modObj[kModNameMax].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{ $min : { a : {$date : 0 } } }");
- ASSERT_OK(mod.init(modObj[kModNameMin].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(ExistingNumber, MaxSameNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$max: {a: 1} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(ExistingNumber, MinSameNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$min: {a: 1} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(ExistingNumber, MaxNumberIsLess) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$max: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(ExistingNumber, MinNumberIsMore) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$min: {a: 2} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(ExistingDouble, MaxSameValInt) {
- Document doc(fromjson("{a: 1.0 }"));
- Mod mod(BSON("$max" << BSON("a" << 1LL)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(ExistingDoubleZero, MaxSameValIntZero) {
- Document doc(fromjson("{a: 0.0 }"));
- Mod mod(BSON("$max" << BSON("a" << 0LL)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(ExistingDoubleZero, MinSameValIntZero) {
- Document doc(fromjson("{a: 0.0 }"));
- Mod mod(BSON("$min" << BSON("a" << 0LL)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(MissingField, MinNumber) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{$min: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 0}"), doc);
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
-}
-
-TEST(ExistingNumber, MinNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$min: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 0}"), doc);
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
-}
-
-TEST(MissingField, MaxNumber) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{$max: {a: 0} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 0}"), doc);
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 0 } }"), logDoc);
-}
-
-TEST(ExistingNumber, MaxNumber) {
- Document doc(fromjson("{a: 1 }"));
- Mod mod(fromjson("{$max: {a: 2} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 2}"), doc);
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 2 } }"), logDoc);
-}
-
-TEST(ExistingDate, MaxDate) {
- Document doc(fromjson("{a: {$date: 0} }"));
- Mod mod(fromjson("{$max: {a: {$date: 123123123}} }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc);
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: {$date: 123123123}} }"), logDoc);
-}
-
-TEST(ExistingEmbeddedDoc, MaxDoc) {
- Document doc(fromjson("{a: {b: 2}}"));
- Mod mod(fromjson("{$max: {a: {b: 3}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a: {b: 3}}}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: {b: 3}} }"), logDoc);
-}
-
-TEST(ExistingEmbeddedDoc, MaxNumber) {
- Document doc(fromjson("{a: {b: 2}}"));
- Mod mod(fromjson("{$max: {a: 3}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-}
-
-TEST(Collation, MinRespectsCollationFromModifierInterfaceOptions) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- Document doc(fromjson("{a: 'cbc'}"));
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- expCtx->setCollator(&collator);
- ModifierInterface::Options options = ModifierInterface::Options::normal(expCtx);
- Mod mod(fromjson("{$min: {a: 'dba'}}"), options);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 'dba'}"), doc);
-}
-
-TEST(Collation, MinRespectsCollationFromSetCollator) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- Document doc(fromjson("{a: 'cbc'}"));
- Mod mod(fromjson("{$min: {a: 'dba'}}"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 'dba'}"), doc);
-}
-
-TEST(Collation, MaxRespectsCollationFromSetCollator) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- Document doc(fromjson("{a: 'cbc'}"));
- Mod mod(fromjson("{$max: {a: 'abd'}}"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(fromjson("{a : 'abd'}"), doc);
-}
-
-TEST(IndexedMod, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$min: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$min: {'a.0.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod mod(fromjson("{$min: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-} // namespace
diff --git a/src/mongo/db/ops/modifier_current_date.cpp b/src/mongo/db/ops/modifier_current_date.cpp
deleted file mode 100644
index 02f67fa7207..00000000000
--- a/src/mongo/db/ops/modifier_current_date.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-/**
- * 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/>.
- *
- * 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/ops/modifier_current_date.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/logical_clock.h"
-#include "mongo/db/logical_time.h"
-#include "mongo/db/service_context.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace str = mongoutils::str;
-
-namespace {
-const char kType[] = "$type";
-const char kDate[] = "date";
-const char kTimestamp[] = "timestamp";
-}
-
-struct ModifierCurrentDate::PreparedState {
- PreparedState(mutablebson::Document& doc) : doc(doc), elemFound(doc.end()), idxFound(0) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-};
-
-ModifierCurrentDate::ModifierCurrentDate() : _pathReplacementPosition(0), _typeIsDate(true) {}
-
-ModifierCurrentDate::~ModifierCurrentDate() {}
-
-Status ModifierCurrentDate::init(const BSONElement& modExpr,
- const Options& opts,
- bool* positional) {
- _updatePath.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_updatePath);
- if (!status.isOK()) {
- return status;
- }
-
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar =
- fieldchecker::isPositional(_updatePath, &_pathReplacementPosition, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _updatePath.dottedField()
- << "'");
- }
-
- // Validate and store the type to produce
- switch (modExpr.type()) {
- case Bool:
- _typeIsDate = true;
- break;
- case Object: {
- const BSONObj argObj = modExpr.embeddedObject();
- const BSONElement typeElem = argObj.getField(kType);
- bool badInput = typeElem.eoo() || !(typeElem.type() == String);
-
- if (!badInput) {
- std::string typeVal = typeElem.String();
- badInput = !(typeElem.String() == kDate || typeElem.String() == kTimestamp);
- if (!badInput)
- _typeIsDate = (typeVal == kDate);
-
- if (!badInput) {
- // Check to make sure only the $type field was given as an arg
- BSONObjIterator i(argObj);
- const bool onlyHasTypeField =
- ((i.next().fieldNameStringData() == kType) && i.next().eoo());
- if (!onlyHasTypeField) {
- return Status(ErrorCodes::BadValue,
- str::stream()
- << "The only valid field of the option is '$type': "
- "{$currentDate: {field : {$type: 'date/timestamp'}}}; "
- << "arg: "
- << argObj);
- }
- }
- }
-
- if (badInput) {
- return Status(ErrorCodes::BadValue,
- "The '$type' string field is required "
- "to be 'date' or 'timestamp': "
- "{$currentDate: {field : {$type: 'date'}}}");
- }
- break;
- }
- default:
- return Status(ErrorCodes::BadValue,
- str::stream() << typeName(modExpr.type())
- << " is not valid type for $currentDate."
- " Please use a boolean ('true')"
- " or a $type expression ({$type: 'timestamp/date'}).");
- }
-
- return Status::OK();
-}
-
-Status ModifierCurrentDate::prepare(mutablebson::Element root,
- 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 (_pathReplacementPosition) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _updatePath.dottedField());
- }
- _updatePath.setPart(_pathReplacementPosition, matchedField);
- }
-
- // 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(
- _updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
- // 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] = &_updatePath;
-
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_updatePath.numParts() - 1)) {
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
- }
-
- return Status::OK();
-}
-
-Status ModifierCurrentDate::apply() const {
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
-
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
- // If the element exists and is the same type, then that is what we want to work with
- mutablebson::Element elemToSet = destExists ? _preparedState->elemFound : doc.end();
-
- if (!destExists) {
- // Creates the final element that's going to be $set in 'doc'.
- // fills in the value with place-holder/empty
-
- elemToSet = _typeIsDate ? doc.makeElementDate(lastPart, Date_t())
- : doc.makeElementTimestamp(lastPart, Timestamp());
-
- 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.
- Status s = pathsupport::createPathAt(
- _updatePath, _preparedState->idxFound, _preparedState->elemFound, elemToSet)
- .getStatus();
- if (!s.isOK())
- return s;
- }
-
- dassert(elemToSet.ok());
-
- // By the time we are here the element is in place and we just need to update the value
- if (_typeIsDate) {
- const mongo::Date_t now = mongo::jsTime();
- Status s = elemToSet.setValueDate(now);
- if (!s.isOK())
- return s;
- } else {
- ServiceContext* service = getGlobalServiceContext();
- auto ts = LogicalClock::get(service)->reserveTicks(1).asTimestamp();
- Status s = elemToSet.setValueTimestamp(ts);
- if (!s.isOK())
- return s;
- }
-
- // Set the elemFound, idxFound to the changed element for oplog logging.
- _preparedState->elemFound = elemToSet;
- _preparedState->idxFound = (_updatePath.numParts() - 1);
-
- return Status::OK();
-}
-
-Status ModifierCurrentDate::log(LogBuilder* logBuilder) const {
- // TODO: None of this checks should be needed unless someone calls if we are a noOp
- // When we cleanup we should build in testing that no-one calls in the noOp case
- const bool destExists = (_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_updatePath.numParts() - 1));
-
- // If the destination doesn't exist then we have nothing to log.
- // This would only happen if apply isn't called or fails when it had to create an element
- if (!destExists)
- return Status::OK();
-
- return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(),
- _preparedState->elemFound);
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_current_date.h b/src/mongo/db/ops/modifier_current_date.h
deleted file mode 100644
index 0b68aa9c75e..00000000000
--- a/src/mongo/db/ops/modifier_current_date.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <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 LogBuilder;
-
-class ModifierCurrentDate : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierCurrentDate);
-
-public:
- ModifierCurrentDate();
- virtual ~ModifierCurrentDate();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming
- * from a $currentDate mod such as
- * {$currentDate: {<fieldname: true/{$type: "date/timestamp"}}.
- * 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, const Options& opts, bool* positional = NULL);
-
- /** Evaluates the validity of applying $currentDate.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /** Updates the node passed in prepare with the results from prepare */
- virtual Status apply() const;
-
- /** Converts the result into a $set */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-private:
- // Access to each component of fieldName that's the target of this mod.
- FieldRef _updatePath;
-
- // 0 or index for $-positional in _updatePath.
- size_t _pathReplacementPosition;
-
- // Is the final type being added a Date or Timestamp?
- bool _typeIsDate;
-
- // State which changes with each call of the mod.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_current_date_test.cpp b/src/mongo/db/ops/modifier_current_date_test.cpp
deleted file mode 100644
index b91aa5505c6..00000000000
--- a/src/mongo/db/ops/modifier_current_date_test.cpp
+++ /dev/null
@@ -1,429 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_current_date.h"
-
-#include <cstdint>
-
-#include "mongo/base/string_data.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/db/logical_clock.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/service_context_noop.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/stdx/memory.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::ModifierCurrentDate;
-using mongo::ModifierInterface;
-using mongo::Status;
-using mongo::StringData;
-using mongo::Timestamp;
-using mongo::fromjson;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-class ModifierCurrentDateTest : public mongo::unittest::Test {
-public:
- ~ModifierCurrentDateTest() override = default;
-
-protected:
- /**
- * Sets up this fixture with a context and a LogicalClock.
- */
- void setUp() override {
- auto service = mongo::getGlobalServiceContext();
-
- auto logicalClock = mongo::stdx::make_unique<mongo::LogicalClock>(service);
- mongo::LogicalClock::set(service, std::move(logicalClock));
- }
- void tearDown() override{};
-};
-
-using Init = ModifierCurrentDateTest;
-using BoolInput = ModifierCurrentDateTest;
-using DateInput = ModifierCurrentDateTest;
-using TimestampInput = ModifierCurrentDateTest;
-using DottedTimestampInput = ModifierCurrentDateTest;
-
-/**
- * Helper to validate oplog entries in the tests below.
- */
-void validateOplogEntry(BSONObj& oplogFormat, Document& doc) {
- // Ensure that the field is the same
- ASSERT_EQUALS(oplogFormat.firstElement().fieldName(), doc.root().leftChild().getFieldName());
-
- // Ensure the field names are the same
- ASSERT_EQUALS(oplogFormat.firstElement().embeddedObject().firstElement().fieldName(),
- doc.root().leftChild().leftChild().getFieldName());
-
- // Ensure the type is the same in the document as the oplog
- ASSERT_EQUALS(oplogFormat.firstElement().embeddedObject().firstElement().type(),
- doc.root().leftChild().leftChild().getType());
-}
-
-/** Helper to build and manipulate a $currentDate mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
- ASSERT_OK(_mod.init(_modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierCurrentDate& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierCurrentDate _mod;
-};
-
-TEST_F(Init, ValidValues) {
- BSONObj modObj;
- ModifierCurrentDate mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- modObj = fromjson("{ $currentDate : { a : true } }");
- ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }");
- ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{ $currentDate : { a : {$type : 'date' } } }");
- ASSERT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST_F(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierCurrentDate mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- // String is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : 'Oct 11, 2001' } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // Array is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : [] } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // Number is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : 1 } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // Regex is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : /1/ } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An object with missing $type field is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : { foo : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An object with extra fields, including the $type field is bad
- modObj = fromjson("{ $currentDate : { a : { $type: 'date', foo : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An object with extra fields, including the $type field is bad
- modObj = fromjson("{ $currentDate : { a : { foo: 4, $type : 'date' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An object with non-date/timestamp $type field is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : { $type : 4 } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // An object with non-date/timestamp $type field is an invalid $currentDate argument
- modObj = fromjson("{ $currentDate : { a : { $type : 'foo' } } }");
- ASSERT_NOT_OK(mod.init(modObj["$currentDate"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST_F(BoolInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { a : true } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(DateInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type: 'date' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(TimestampInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << ts);
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(BoolInput, ExistingStringDoc) {
- Document doc(fromjson("{ a: 'a' }"));
- Mod mod(fromjson("{ $currentDate : { a : true } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(BoolInput, ExistingDateDoc) {
- Document doc(fromjson("{ a: {$date: 0 } }"));
- Mod mod(fromjson("{ $currentDate : { a : true } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(DateInput, ExistingDateDoc) {
- Document doc(fromjson("{ a: {$date: 0 } }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type: 'date' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- BSONObj olderDateObj = fromjson("{ a : { $date : 0 } }");
- ASSERT_OK(mod.apply());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $date : 0 } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(TimestampInput, ExistingDateDoc) {
- Document doc(fromjson("{ a: {$date: 0 } }"));
- Mod mod(fromjson("{ $currentDate : { a : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << ts);
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled()); // Same Size as Date
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { a : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(TimestampInput, ExistingEmbeddedDateDoc) {
- Document doc(fromjson("{ a: {b: {$date: 0 } } }"));
- Mod mod(fromjson("{ $currentDate : { 'a.b' : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a.b", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << BSON("b" << ts));
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled()); // Same Size as Date
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { 'a.b' : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(DottedTimestampInput, EmptyStartDoc) {
- Document doc(fromjson("{ }"));
- Mod mod(fromjson("{ $currentDate : { 'a.b' : {$type : 'timestamp' } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS("a.b", execInfo.fieldRef[0]->dottedField());
-
- mongo::Timestamp ts;
- BSONObj olderDateObj = BSON("a" << BSON("b" << ts));
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_BSONOBJ_LT(olderDateObj, doc.getObject());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- BSONObj oplogFormat = fromjson("{ $set : { 'a.b' : { $timestamp : {t:0, i:0} } } }");
- validateOplogEntry(oplogFormat, logDoc);
-}
-
-TEST_F(BoolInput, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$currentDate: {'a.1.c': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST_F(BoolInput, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$currentDate: {'a.0.c': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST_F(BoolInput, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod mod(fromjson("{$currentDate: {'a.1.c': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-} // namespace
diff --git a/src/mongo/db/ops/modifier_inc.cpp b/src/mongo/db/ops/modifier_inc.cpp
deleted file mode 100644
index e708afeb941..00000000000
--- a/src/mongo/db/ops/modifier_inc.cpp
+++ /dev/null
@@ -1,283 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_inc.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-struct ModifierInc::PreparedState {
- PreparedState(mutablebson::Document& doc)
- : doc(doc), idxFound(0), elemFound(doc.end()), newValue(), noOp(false) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-
- // Value to be applied
- SafeNum newValue;
-
- // This $inc is a no-op?
- bool noOp;
-};
-
-ModifierInc::ModifierInc(ModifierIncMode mode)
- : ModifierInterface(), _mode(mode), _fieldRef(), _posDollar(0), _val() {}
-
-ModifierInc::~ModifierInc() {}
-
-Status ModifierInc::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- //
- // 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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- //
- // value analysis
- //
-
- if (!modExpr.isNumber()) {
- // TODO: Context for mod error messages would be helpful
- // include mod code, etc.
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "Cannot " << (_mode == MODE_INC ? "increment" : "multiply")
- << " with non-numeric argument: {"
- << modExpr
- << "}");
- }
-
- _val = modExpr;
- dassert(_val.isValid());
-
- return Status::OK();
-}
-
-Status ModifierInc::prepare(mutablebson::Element root,
- 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // 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);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
- // 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 < (_fieldRef.numParts() - 1)) {
- // For multiplication, we treat ops against missing as yielding zero. We take
- // advantage here of the promotion rules for SafeNum; the expression below will
- // always yield a zero of the same type of operand that the user provided
- // (e.g. double).
- if (_mode == MODE_MUL)
- _preparedState->newValue *= SafeNum(static_cast<int32_t>(0));
-
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
-
- 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()) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "Cannot apply " << (_mode == MODE_INC ? "$inc" : "$mul")
- << " to a value of non-numeric type. {"
- << idElem.toString()
- << "} has the field '"
- << _preparedState->elemFound.getFieldName()
- << "' of non-numeric type "
- << typeName(_preparedState->elemFound.getType()));
- }
- const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum();
-
- // Update newValue w.r.t to the current value of the found element.
- if (_mode == MODE_INC)
- _preparedState->newValue += currentValue;
- else
- _preparedState->newValue *= currentValue;
-
- // If the result of the addition is invalid, we must return an error.
- if (!_preparedState->newValue.isValid()) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(ErrorCodes::BadValue,
- str::stream() << "Failed to apply $inc operations to current value ("
- << currentValue.debugString()
- << ") for document {"
- << idElem.toString()
- << "}");
- }
-
- // If the values are identical (same type, same value), then this is a no-op.
- if (_preparedState->newValue.isIdentical(currentValue)) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
-
- 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 == (_fieldRef.numParts() - 1)) {
- return _preparedState->elemFound.setValueSafeNum(_preparedState->newValue);
- }
-
- //
- // 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)
- .getStatus();
-}
-
-Status ModifierInc::log(LogBuilder* logBuilder) const {
- dassert(_preparedState->newValue.isValid());
-
- // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
- // We start by creating the {$set: ...} Element.
- mutablebson::Document& doc = logBuilder->getDocument();
-
- // Then we create the {<fieldname>: <value>} Element.
- mutablebson::Element logElement =
- doc.makeElementSafeNum(_fieldRef.dottedField(), _preparedState->newValue);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to "
- << (_mode == MODE_INC ? "$inc" : "$mul")
- << " oplog entry: "
- << "set '"
- << _fieldRef.dottedField()
- << "' -> "
- << _preparedState->newValue.debugString());
- }
-
- // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} segment.
- return logBuilder->addToSets(logElement);
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_inc.h b/src/mongo/db/ops/modifier_inc.h
deleted file mode 100644
index 8f10c28b693..00000000000
--- a/src/mongo/db/ops/modifier_inc.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <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 LogBuilder;
-
-class ModifierInc : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierInc);
-
-public:
- // TODO: This is a shortcut to implementing $mul by hijacking $inc. In the near future,
- // we should consider either pulling $mul into its own operator, or creating a general
- // purpose "numeric binary op" operator. Potentially, that operator could also subsume
- // $bit (thought there are some subtleties, like that $bit can have multiple
- // operations, and doing so with arbirary math operations introduces potential
- // associativity difficulties). At the very least, if this mechanism is retained, then
- // this class should be renamed at some point away from ModifierInc.
- enum ModifierIncMode { MODE_INC, MODE_MUL };
-
- ModifierInc(ModifierIncMode mode = MODE_INC);
- 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, const Options& opts, bool* positional = NULL);
-
- /** 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, 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(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-private:
- const ModifierIncMode _mode;
-
- // 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;
- std::unique_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
deleted file mode 100644
index 5f941a3ae8b..00000000000
--- a/src/mongo/db/ops/modifier_inc_test.cpp
+++ /dev/null
@@ -1,673 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_inc.h"
-
-#include <cstdint>
-
-#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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/platform/decimal128.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::Decimal128;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::ModifierInc;
-using mongo::ModifierInterface;
-using mongo::NumberInt;
-using mongo::Status;
-using mongo::StringData;
-using mongo::fromjson;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-using mongo::mutablebson::countChildren;
-
-/** Helper to build and manipulate a $inc/$mul modifier */
-class Mod {
-public:
- explicit Mod(BSONObj modObj)
- : _modObj(modObj),
- _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$mul")
- ? ModifierInc::MODE_MUL
- : ModifierInc::MODE_INC) {
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierInc& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierInc _mod;
-};
-
-TEST(Init, FailToInitWithInvalidValue) {
- BSONObj modObj;
- ModifierInc mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- // String is an invalid increment argument
- modObj = fromjson("{ $inc : { a : '' } }");
- ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // Object is an invalid increment argument
- modObj = fromjson("{ $inc : { a : {} } }");
- ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- // Array is an invalid increment argument
- modObj = fromjson("{ $inc : { a : [] } }");
- ASSERT_NOT_OK(mod.init(modObj["$inc"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-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(Init, InitParsesNumberDecimal) {
- Mod incMod(BSON("$inc" << BSON("a" << Decimal128(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(doc.isInPlaceModeEnabled());
- 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.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
-}
-
-TEST(SimpleMod, LogWithoutApplyEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod incMod(fromjson("{ $inc: { a : 1 }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
-}
-
-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_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 3 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 3 } }"), logDoc);
-}
-
-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_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : { b : 3 } }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { 'a.b' : 3 } }"), logDoc);
-}
-
-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);
-}
-
-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);
-}
-
-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);
-}
-
-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);
-}
-
-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);
-}
-
-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);
-}
-
-TEST(NoOp, Decimal) {
- Document doc(BSON("a" << Decimal128("1.0")));
- Mod incMod(BSON("$inc" << BSON("a" << Decimal128("0.0"))));
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-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_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1 }"), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1 } }"), logDoc);
- 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_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1.0 }"), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1.0 } }"), logDoc);
- 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_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 1.0 }"), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 1.0 } }"), logDoc);
- 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_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 2.0 }"), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : 2.0 } }"), logDoc);
- ASSERT_EQUALS(mongo::NumberDouble, logDoc.root()["$set"]["a"].getType());
-}
-
-TEST(Upcasting, UpcastIntToDecimal) {
- // Checks that $inc : NumberDecimal(0) turns a NumberInt into a NumberDecimal 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 : NumberDecimal(\"0\") }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"1.0\") }"), doc);
- ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"1.0\") }}"), logDoc);
- ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType());
-}
-
-TEST(Upcasting, UpcastLongToDecimal) {
- // Checks that $inc : NumberDecimal(0) turns a NumberLong into a NumberDecimal 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 : NumberDecimal(\"0\") }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"1.0\") }"), doc);
- ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"1.0\") }}"), logDoc);
- ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType());
-}
-
-TEST(Upcasting, UpcastDoubleToDecimal) {
- // Checks that $inc : NumberDecimal(0) turns a double into a NumberDecimal and logs it
- // correctly.
- Document doc(BSON("a" << static_cast<double>(1.0)));
- ASSERT_EQUALS(mongo::NumberDouble, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : NumberDecimal(\"0\") }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"1.0\") }"), doc);
- ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"1.0\") }}"), logDoc);
- ASSERT_EQUALS(mongo::NumberDecimal, logDoc.root()["$set"]["a"].getType());
-}
-
-TEST(Upcasting, DecimalsStayDecimals) {
- // Checks that $inc : NumberDecimal(1) keeps a NumberDecimal as a NumberDecimal and logs it
- // correctly.
- Document doc(BSON("a" << mongo::Decimal128("1.0")));
- ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType());
-
- Mod incMod(fromjson("{ $inc : { a : NumberDecimal(\"1\") }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : NumberDecimal(\"2.0\") }"), doc);
- ASSERT_EQUALS(mongo::NumberDecimal, doc.root()["a"].getType());
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : NumberDecimal(\"2.0\") }}"), logDoc);
- ASSERT_EQUALS(mongo::NumberDecimal, 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);
-
- const long long target_value = static_cast<long long>(initial_value) + 1;
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << target_value), doc);
-}
-
-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);
-
- const long long target_value = static_cast<long long>(initial_value) - 1;
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << target_value), doc);
-}
-
-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_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc1.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 2 }"), doc1);
-
- ASSERT_OK(incMod.prepare(doc2.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc2.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 2 }"), doc2);
-}
-
-// Given the current implementation of $mul, we really only need one test for
-// $mul. However, in the future, we should probably write additional ones, or, perhaps find
-// a way to run all the above tests in both modes.
-TEST(Multiplication, ApplyAndLogSimpleDocument) {
- Document doc(fromjson("{ a : { b : 2 } }"));
- Mod incMod(fromjson("{ $mul: { 'a.b' : 3 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : { b : 6 } }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { 'a.b' : 6 } }"), logDoc);
-}
-
-TEST(Multiplication, ApplyAndLogMissingElement) {
- Document doc(fromjson("{ a : 0 }"));
- Mod incMod(fromjson("{ $mul : { b : 3 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : 0, b : 0 }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(incMod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { b : 0 } }"), logDoc);
-}
-
-TEST(Multiplication, ApplyMissingElementInt) {
- const int int_zero = 0;
- const int int_three = 3;
-
- Document doc(BSON("a" << int_zero));
- Mod incMod(BSON("$mul" << BSON("b" << int_three)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << int_zero << "b" << int_zero), doc);
- ASSERT_EQUALS(mongo::NumberInt, doc.root().rightChild().getType());
-}
-
-TEST(Multiplication, ApplyMissingElementLongLong) {
- const long long ll_zero = 0;
- const long long ll_three = 3;
-
- Document doc(BSON("a" << ll_zero));
- Mod incMod(BSON("$mul" << BSON("b" << ll_three)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << ll_zero << "b" << ll_zero), doc);
- ASSERT_EQUALS(mongo::NumberLong, doc.root().rightChild().getType());
-}
-
-TEST(Multiplication, ApplyMissingElementDouble) {
- const double double_zero = 0;
- const double double_three = 3;
-
- Document doc(BSON("a" << double_zero));
- Mod incMod(BSON("$mul" << BSON("b" << double_three)));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(incMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(incMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(BSON("a" << double_zero << "b" << 0), doc);
- ASSERT_EQUALS(mongo::NumberDouble, doc.root().rightChild().getType());
-}
-
-TEST(IndexedMod, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$inc: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$inc: {'a.0.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod mod(fromjson("{$inc: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-} // namespace
diff --git a/src/mongo/db/ops/modifier_interface.h b/src/mongo/db/ops/modifier_interface.h
deleted file mode 100644
index b5705b536fe..00000000000
--- a/src/mongo/db/ops/modifier_interface.h
+++ /dev/null
@@ -1,223 +0,0 @@
-/**
- * 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/>.
- *
- * 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/base/status.h"
-#include "mongo/base/string_data.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/pipeline/expression_context.h"
-
-namespace mongo {
-
-class CollatorInterface;
-class LogBuilder;
-
-/**
- * Abstract base class for update "modifiers" (a.k.a "$ operators"). To create a new
- * operator, implement a new derived class.
- *
- * A typical call sequence for the class is:
- *
- * + init() with the mod arguments
- *
- * + For each document that is being touched on that update, the following methods are
- * going to be called once for that document and in the order the calls appear here.
- *
- * + prepare() to check if mod is viable over the document
- *
- * + apply(), effectively computing the update
- *
- * + log() registering the change in the log for replication purposes
- *
- * Again, a modifier implementation may rely on these last three calls being made and in
- * that particular order and therefore can keep and reuse state between these calls, when
- * appropriate.
- *
- * TODO:
- * For a reference implementation, see modifier_identity.{h,cpp} used in tests.
- */
-class ModifierInterface {
-public:
- virtual ~ModifierInterface() {}
-
- struct Options;
- /**
- * Returns OK and extracts the parameters for this given mod from 'modExpr'. For
- * instance, for a $inc, extracts the increment value. The init() method would be
- * called only once per operand, that is, if a { $inc: { a: 1, b: 1 } } is issued,
- * there would be one instance of the operator working on 'a' and one on 'b'. In each
- * case, init() would be called once with the respective bson element.
- *
- * If 'modExpr' is invalid, returns an error status with a reason description.
- *
- * The optional bool out parameter 'positional', if provided, will be set to 'true' if
- * the mod requires matched field details to be provided when calling 'prepare'. The
- * field is optional since this is a hint to the caller about what work is needed to
- * correctly invoke 'prepare'. It is always legal to provide any match details
- * unconditionally. The value set in 'positional' if any, is only meaningful if 'init'
- * returns an OK status.
- *
- * Note:
- *
- * + An operator may assume the modExpr passed here will be unchanged throughout all
- * the mod object lifetime and also that the modExrp's lifetime exceeds the life
- * time of this mod. Therefore, taking references to elements inside modExpr is
- * valid.
- */
- virtual Status init(const BSONElement& modExpr,
- const Options& opts,
- bool* positional = NULL) = 0;
-
- /**
- * Returns OK if it would be correct to apply this mod over the document 'root' (e.g, if
- * we're $inc-ing a field, is that field numeric in the current doc?).
- *
- * If the field this mod is targeted to contains a $-positional parameter, that value
- * can be bound with 'matchedField', passed by the caller.
- *
- * In addition, the call also identifies which fields(s) of 'root' the mod is interested
- * in changing (note that the modifier may want to add a field that's not present in
- * the document). The call also determines whether it could modify the document in
- * place and whether it is a no-op for the given document. All this information is in
- * the passed 'execInfo', which is filled inside the call.
- *
- * If the mod cannot be applied over 'root', returns an error status with a reason
- * description.
- *
- * Note that you must provide a meaningful 'matchedField' here, unless 'init' set
- * 'positional' to 'false', in which case you may pass an empty StringData object.
- */
- struct ExecInfo;
- virtual Status prepare(mutablebson::Element root,
- StringData matchedField,
- /* IN-OUT */ ExecInfo* execInfo) = 0;
-
- /**
- * Returns OK and modifies (or adds) an element (or elements) from the 'root' passed on
- * the prepareMod call. This may act on multiple fields but should only be called once
- * per operator.
- *
- * For this call to be issued, the call to 'prepareElem' must have necessarily turned
- * off 'ExecInfo.noOp', ie this mod over this document is not a no-op.
- *
- * If the mod could not be applied, returns an error status with a reason description.
- */
- virtual Status apply() const = 0;
-
- /**
- * Returns OK and records the result of this mod in the provided LogBuilder. The mod
- * must have kept enough state to be able to produce the log record (see idempotency
- * note below). This call may be issued even if apply() was not.
- *
- * If the mod could not be logged, returns an error status with a reason description.
- *
- * Idempotency Note:
- *
- * + The modifier must log a mod that is idempotent, ie, applying it more than once
- * to a base collection would produce the same result as applying it only once. For
- * example, a $inc can be switched to a $set for the resulting incremented value,
- * for logging purposes. An array based operator may check the contents of the
- * array before operating on it.
- */
- virtual Status log(LogBuilder* logBuilder) const = 0;
-
- /**
- * Set the collation on the modifier. This is a no-op on modifiers that are not
- * collation-aware.
- *
- * setCollator() should update any initialization that occured during init() to respect the
- * provided collator, which may be different than the collator provided in the modifier
- * options.
- *
- * If setCollator() is called, it is required that the current collator of the modifier is
- * the simple collator (nullptr).
- *
- * The collator must outlive the modifier interface.
- */
- virtual void setCollator(const CollatorInterface* collator) = 0;
-};
-
-/**
- * Options used to control Modifier behavior
- */
-struct ModifierInterface::Options {
- Options(bool fromOpLog, bool ofs, boost::intrusive_ptr<ExpressionContext> expCtx)
- : fromOplogApplication(fromOpLog), enforceOkForStorage(ofs), expCtx(std::move(expCtx)) {}
-
- static Options normal(boost::intrusive_ptr<ExpressionContext> expCtx) {
- return Options(false, true, std::move(expCtx));
- }
- static Options fromRepl(boost::intrusive_ptr<ExpressionContext> expCtx) {
- return Options(true, false, std::move(expCtx));
- }
-
- bool fromOplogApplication = false;
- bool enforceOkForStorage = true;
- boost::intrusive_ptr<ExpressionContext> expCtx;
-};
-
-struct ModifierInterface::ExecInfo {
- static const int MAX_NUM_FIELDS = 2;
-
- /**
- * An update mod may specify that it wishes to the applied only if the context
- * of the update turns out a certain way.
- */
- enum UpdateContext {
- // This mod wants to be applied only if the update turns out to be an insert.
- INSERT_CONTEXT,
-
- // This mod wants to be applied only if the update is not an insert.
- UPDATE_CONTEXT,
-
- // This mod doesn't care if the update will be an update or an upsert.
- ANY_CONTEXT
- };
-
- ExecInfo() : noOp(false), context(ANY_CONTEXT) {
- for (int i = 0; i < MAX_NUM_FIELDS; i++) {
- fieldRef[i] = NULL;
- indexOfArrayWithNewElement[i] = boost::none;
- }
- }
-
- // The fields of concern to the driver: no other op may modify the fields listed here.
- FieldRef* fieldRef[MAX_NUM_FIELDS]; // not owned here
-
- // For each modified field ref, the index of the path component representing an existing array
- // that gained a new element.
- boost::optional<size_t> indexOfArrayWithNewElement[MAX_NUM_FIELDS];
-
- bool noOp;
- UpdateContext context;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pop.cpp b/src/mongo/db/ops/modifier_pop.cpp
deleted file mode 100644
index faac70222d5..00000000000
--- a/src/mongo/db/ops/modifier_pop.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_pop.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-struct ModifierPop::PreparedState {
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc),
- elementToRemove(doc.end()),
- pathFoundIndex(0),
- pathFoundElement(doc.end()) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Element to be removed
- mutablebson::Element elementToRemove;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t pathFoundIndex;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element pathFoundElement;
-};
-
-ModifierPop::ModifierPop() : _fieldRef(), _positionalPathIndex(0), _fromTop(false) {}
-
-ModifierPop::~ModifierPop() {}
-
-Status ModifierPop::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- //
- // field name analysis
- //
-
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // there are no empty parts.
- _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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_positionalPathIndex, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- //
- // value analysis
- //
-
- // TODO: tighten validation to numbers and just 1/-1 explicitly
- // if (!modExpr.isNumber()) {
- // return Status(ErrorCodes::BadValue, "Must be a number");
- //}
-
- _fromTop = (modExpr.isNumber() && modExpr.number() < 0) ? true : false;
-
- return Status::OK();
-}
-
-Status ModifierPop::prepare(mutablebson::Element root,
- 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 (_positionalPathIndex) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_positionalPathIndex, matchedField);
- }
-
- // Locate the field name in 'root'. Note that if we don't have the full path in the
- // doc, there isn't anything to unset, really.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement);
- // Check if we didn't find the full path
- if (status.isOK()) {
- const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
- if (!destExists) {
- execInfo->noOp = true;
- } else {
- // If the path exists, we require the target field to be already an
- // array.
- if (_preparedState->pathFoundElement.getType() != Array) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "Can only $pop from arrays. {" << idElem.toString()
- << "} has the field '"
- << _preparedState->pathFoundElement.getFieldName()
- << "' of non-array type "
- << typeName(_preparedState->pathFoundElement.getType()));
- }
-
- // No children, nothing to do -- not an error state
- if (!_preparedState->pathFoundElement.hasChildren()) {
- execInfo->noOp = true;
- } else {
- _preparedState->elementToRemove = _fromTop
- ? _preparedState->pathFoundElement.leftChild()
- : _preparedState->pathFoundElement.rightChild();
- }
- }
- } else {
- // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
- execInfo->noOp = true;
- _preparedState->pathFoundElement = root.getDocument().end();
-
- // okay if path not found
- if (status.code() == ErrorCodes::NonExistentPath)
- status = Status::OK();
- }
-
- // Let the caller know what field we care about
- execInfo->fieldRef[0] = &_fieldRef;
-
- return status;
-}
-
-Status ModifierPop::apply() const {
- return _preparedState->elementToRemove.remove();
-}
-
-Status ModifierPop::log(LogBuilder* logBuilder) const {
- // log document
- mutablebson::Document& doc = logBuilder->getDocument();
- const bool pathExists = _preparedState->pathFoundElement.ok() &&
- (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
-
- if (!pathExists)
- return logBuilder->addToUnsets(_fieldRef.dottedField());
-
- // value for the logElement ("field.path.name": <value>)
- mutablebson::Element logElement =
- doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _preparedState->pathFoundElement);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to $pop oplog entry: "
- << "set '"
- << _fieldRef.dottedField()
- << "' -> "
- << _preparedState->pathFoundElement.toString());
- }
- return logBuilder->addToSets(logElement);
-}
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pop.h b/src/mongo/db/ops/modifier_pop.h
deleted file mode 100644
index 5593360f300..00000000000
--- a/src/mongo/db/ops/modifier_pop.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-class ModifierPop : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPop);
-
-public:
- ModifierPop();
- virtual ~ModifierPop();
-
- /**
- * The format of this modifier ($pop) is {<fieldname>: <value>}.
- * If the value is number and greater than -1 then an element is removed from the bottom,
- * otherwise the top. Currently the value can be any anything but we document
- * the use of the numbers "1, -1" only.
- *
- * Ex. $pop: {'a':1} will remove the last item from this array: [1,2,3] -> [1,2]
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
-
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
-
- virtual Status apply() const;
-
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-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 _positionalPathIndex;
-
- // element position to remove from
- bool _fromTop;
-
- // The instance of the field in the provided doc.
- // This data is valid after prepare, for use by log and apply
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pop_test.cpp b/src/mongo/db/ops/modifier_pop_test.cpp
deleted file mode 100644
index f899cedcdd4..00000000000
--- a/src/mongo/db/ops/modifier_pop_test.cpp
+++ /dev/null
@@ -1,324 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_pop.h"
-
-#include <cstdint>
-
-#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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::Array;
-using mongo::BSONObj;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::fromjson;
-using mongo::ModifierInterface;
-using mongo::ModifierPop;
-using mongo::Status;
-using mongo::StringData;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate a $pop mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) {
- _modObj = modObj;
- ASSERT_OK(_mod.init(_modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierPop& mod() {
- return _mod;
- }
-
- BSONObj modObj() {
- return _modObj;
- }
-
-private:
- ModifierPop _mod;
- BSONObj _modObj;
-};
-
-//
-// Test init values which aren't numbers.
-// These are going to cause a pop from the bottom.
-//
-TEST(Init, StringArg) {
- BSONObj modObj = fromjson("{$pop: {a: 'hi'}}");
- ModifierPop mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, BoolTrueArg) {
- BSONObj modObj = fromjson("{$pop: {a: true}}");
- ModifierPop mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, BoolFalseArg) {
- BSONObj modObj = fromjson("{$pop: {a: false}}");
- ModifierPop mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$pop"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(MissingField, AllButApply) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {s: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "s");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$unset: {'s': true}}"), logDoc);
-}
-
-TEST(SimpleMod, PrepareBottom) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareApplyBottomO) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 0}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a: [1]}")), doc);
-}
-
-TEST(SimpleMod, PrepareTop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: -1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, ApplyTopPop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: -1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a: [2]}")), doc);
-}
-
-TEST(SimpleMod, ApplyBottomPop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a: [1]}")), doc);
-}
-
-TEST(SimpleMod, ApplyLogBottomPop) {
- Document doc(fromjson("{a: [1,2]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[1]}")), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
-}
-
-TEST(EmptyArray, PrepareNoOp) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(SingleElemArray, ApplyLog) {
- Document doc(fromjson("{a: [1]}"));
- Mod mod(fromjson("{$pop: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[]}")), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: {a: []}}"), logDoc);
-}
-
-TEST(ArrayOfArray, ApplyLogPop) {
- Document doc(fromjson("{a: [[1,2], 1]}"));
- Mod mod(fromjson("{$pop: {'a.0': 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[[1], 1]}")), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: { 'a.0': [1]}}"), logDoc);
-}
-
-TEST(ArrayOfArray, ApplyLogPopOnlyElement) {
- Document doc(fromjson("{a: [[1], 1]}"));
- Mod mod(fromjson("{$pop: {'a.0': 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(("{a:[[], 1]}")), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$set: { 'a.0': []}}"), logDoc);
-}
-
-TEST(Prepare, MissingPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pop : { b : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-// from SERVER-12846
-TEST(Prepare, MissingArrayElementPath) {
- Document doc(fromjson("{_id : 1, a : [1, 2]}"));
- Mod mod(fromjson("{ $pop : { 'a.3' : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(Prepare, FromArrayElementPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pop : { 'a.0' : 1 } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_pull.cpp b/src/mongo/db/ops/modifier_pull.cpp
deleted file mode 100644
index 2d67fdf9505..00000000000
--- a/src/mongo/db/ops/modifier_pull.cpp
+++ /dev/null
@@ -1,293 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_pull.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/db/matcher/expression_parser.h"
-#include "mongo/db/query/collation/collator_interface.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-struct ModifierPull::PreparedState {
- PreparedState(mb::Document& doc)
- : doc(doc), idxFound(0), elemFound(doc.end()), elementsToRemove(), noOp(false) {}
-
- // Document that is going to be changed.
- mb::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mb::Element elemFound;
-
- // Values to be removed.
- std::vector<mb::Element> elementsToRemove;
-
- // True if this update is a no-op
- bool noOp;
-};
-
-ModifierPull::ModifierPull()
- : ModifierInterface(),
- _fieldRef(),
- _posDollar(0),
- _exprElt(),
- _exprObj(),
- _matchExpr(),
- _matcherOnPrimitive(false),
- _preparedState() {}
-
-ModifierPull::~ModifierPull() {}
-
-Status ModifierPull::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- // 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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- _exprElt = modExpr;
-
- _collator = opts.expCtx->getCollator();
-
- // If the element in the mod is actually an object or a regular expression, we need to
- // build a matcher, instead of just doing an equality comparision.
- if ((_exprElt.type() == mongo::Object) || (_exprElt.type() == mongo::RegEx)) {
- if (_exprElt.type() == Object) {
- _exprObj = _exprElt.embeddedObject();
-
- // If not is not a query operator, then it is a primitive.
- _matcherOnPrimitive = (MatchExpressionParser::parsePathAcceptingKeyword(
- _exprObj.firstElement(), PathAcceptingKeyword::EQUALITY) !=
- PathAcceptingKeyword::EQUALITY);
-
- // If the object is primitive then wrap it up into an object.
- if (_matcherOnPrimitive)
- _exprObj = BSON("" << _exprObj);
- } else {
- // For a regex, we also need to wrap and treat like a primitive.
- _matcherOnPrimitive = true;
- _exprObj = _exprElt.wrap("");
- }
-
- // Build the matcher around the object we built above. Currently, we do not allow $pull
- // operations to contain $text/$where/$geoNear/$near/$nearSphere/$expr/$jsonSchema clauses.
- StatusWithMatchExpression parseResult =
- MatchExpressionParser::parse(_exprObj,
- opts.expCtx,
- ExtensionsCallbackNoop(),
- MatchExpressionParser::kBanAllSpecialFeatures);
- if (!parseResult.isOK()) {
- return parseResult.getStatus();
- }
-
- _matchExpr = std::move(parseResult.getValue());
- }
-
- return Status::OK();
-}
-
-void ModifierPull::setCollator(const CollatorInterface* collator) {
- invariant(!_collator);
- _collator = collator;
- if (_matchExpr) {
- _matchExpr->setCollator(_collator);
- }
-}
-
-Status ModifierPull::prepare(mb::Element root, 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'.
- 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;
-
- if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // If no target element exists, then there is nothing to do here.
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
-
- // This operation only applies to arrays
- if (_preparedState->elemFound.getType() != mongo::Array)
- return Status(ErrorCodes::BadValue, "Cannot apply $pull to a non-array value");
-
- // If the array is empty, there is nothing to pull, so this is a noop.
- if (!_preparedState->elemFound.hasChildren()) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
-
- // Walk the values in the array
- mb::Element cursor = _preparedState->elemFound.leftChild();
- while (cursor.ok()) {
- if (isMatch(cursor))
- _preparedState->elementsToRemove.push_back(cursor);
- cursor = cursor.rightSibling();
- }
-
- // If we didn't find any elements to add, then this is a no-op, and therefore in place.
- if (_preparedState->elementsToRemove.empty()) {
- _preparedState->noOp = execInfo->noOp = true;
- }
-
- return Status::OK();
-}
-
-Status ModifierPull::apply() const {
- dassert(_preparedState->noOp == false);
-
- dassert(_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts() - 1));
-
- std::vector<mb::Element>::const_iterator where = _preparedState->elementsToRemove.begin();
- const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToRemove.end();
- for (; where != end; ++where)
- const_cast<mb::Element&>(*where).remove().transitional_ignore();
-
- return Status::OK();
-}
-
-Status ModifierPull::log(LogBuilder* logBuilder) const {
- mb::Document& doc = logBuilder->getDocument();
-
- if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // If we didn't find the element that we wanted to pull from, we log an unset for
- // that element.
- return logBuilder->addToUnsets(_fieldRef.dottedField());
-
- } else {
- // TODO: This is copied more or less identically from $push. As a result, it copies the
- // behavior in $push that relies on 'apply' having been called unless this is a no-op.
-
- // TODO We can log just a positional unset in several cases. For now, let's just log
- // the full resulting array.
-
- // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
- // 'logRoot'. We start by creating the {$set: ...} Element.
-
- // Then we create the {<fieldname>:[]} Element, that is, an empty array.
- mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $pull mod");
- }
-
- mb::Element curr = _preparedState->elemFound.leftChild();
- while (curr.ok()) {
- dassert(curr.hasValue());
-
- // We need to copy each array entry from the resulting document to the log
- // document.
- mb::Element currCopy = doc.makeElementWithNewFieldName(StringData(), curr.getValue());
- if (!currCopy.ok()) {
- return Status(ErrorCodes::InternalError, "could create copy element");
- }
- Status status = logElement.pushBack(currCopy);
- if (!status.isOK()) {
- return Status(ErrorCodes::BadValue, "could not append entry for $pull log");
- }
- curr = curr.rightSibling();
- }
-
- return logBuilder->addToSets(logElement);
- }
-}
-
-bool ModifierPull::isMatch(mutablebson::ConstElement element) {
- // TODO: We are assuming that 'element' hasValue is true. That might be OK if the
- // conflict detection logic will prevent us from ever seeing a deserialized element,
- // but are we sure about that?
-
- dassert(element.hasValue());
-
- if (!_matchExpr)
- return (element.compareWithBSONElement(_exprElt, _collator, false) == 0);
-
- if (_matcherOnPrimitive) {
- // TODO: This is kinda slow.
- BSONObj candidate = element.getValue().wrap("");
- return _matchExpr->matchesBSON(candidate);
- }
-
- if (element.getType() != Object)
- return false;
-
- return _matchExpr->matchesBSON(element.getValueObject());
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull.h b/src/mongo/db/ops/modifier_pull.h
deleted file mode 100644
index 6ed7cc12145..00000000000
--- a/src/mongo/db/ops/modifier_pull.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * 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/>.
- *
- * 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/base/disallow_copying.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class CollatorInterface;
-class MatchExpression;
-
-class ModifierPull : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPull);
-
-public:
- ModifierPull();
- virtual ~ModifierPull();
-
- /** Evaluates the array items to be removed and the match expression. */
- virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
-
- /** Decides which portion of the array items will be removed from the provided element */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /** Updates the Element used in prepare with the effects of the $pull operation. */
- virtual Status apply() const;
-
- /** Converts the effects of this $pull into one or more equivalent $unset operations. */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator);
-
-private:
- bool isMatch(mutablebson::ConstElement element);
-
- // 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;
-
- // If we aren't using a matcher, we just keep modExpr as _exprElt and use that to match
- // with woCompare.
- BSONElement _exprElt;
-
- // If we are using a matcher, we need to keep around a BSONObj for it.
- BSONObj _exprObj;
-
- // If we are using the matcher, this is the match expression we built around _exprObj.
- std::unique_ptr<MatchExpression> _matchExpr;
- bool _matcherOnPrimitive;
-
- // The collator which must be used for matching strings. Null if we should use a simple binary
- // comparison.
- const CollatorInterface* _collator = nullptr;
-
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull_all.cpp b/src/mongo/db/ops/modifier_pull_all.cpp
deleted file mode 100644
index 21455cb66a0..00000000000
--- a/src/mongo/db/ops/modifier_pull_all.cpp
+++ /dev/null
@@ -1,243 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_pull_all.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/query/collation/collator_interface.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-using std::vector;
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-struct ModifierPullAll::PreparedState {
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc),
- pathFoundIndex(0),
- pathFoundElement(doc.end()),
- applyCalled(false),
- elementsToRemove() {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t pathFoundIndex;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element pathFoundElement;
-
- bool applyCalled;
-
- // Elements to be removed
- vector<mutablebson::Element> elementsToRemove;
-};
-
-namespace {
-
-struct mutableElementEqualsBSONElement : std::unary_function<BSONElement, bool> {
- mutableElementEqualsBSONElement(const mutablebson::Element& elem,
- const CollatorInterface* collator)
- : _what(elem), _collator(collator) {}
- bool operator()(const BSONElement& elem) const {
- return _what.compareWithBSONElement(elem, _collator, false) == 0;
- }
- const mutablebson::Element& _what;
- const CollatorInterface* _collator = nullptr;
-};
-} // namespace
-
-ModifierPullAll::ModifierPullAll() : _fieldRef(), _positionalPathIndex(0), _elementsToFind() {}
-
-ModifierPullAll::~ModifierPullAll() {}
-
-Status ModifierPullAll::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- //
- // field name analysis
- //
-
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // there are no empty parts.
- _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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_positionalPathIndex, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- //
- // value analysis
- //
-
- if (modExpr.type() != Array) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "$pullAll requires an array argument but was given a "
- << typeName(modExpr.type()));
- }
-
- // store the stuff to remove later
- _elementsToFind = modExpr.Array();
- setCollator(opts.expCtx->getCollator());
-
- return Status::OK();
-}
-
-Status ModifierPullAll::prepare(mutablebson::Element root,
- 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 (_positionalPathIndex) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_positionalPathIndex, matchedField);
- }
-
- // Locate the field name in 'root'. Note that if we don't have the full path in the
- // doc, there isn't anything to unset, really.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement);
- // Check if we didn't find the full path
- if (status.isOK()) {
- const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
-
- if (!destExists) {
- execInfo->noOp = true;
- } else {
- // If the path exists, we require the target field to be already an
- // array.
- if (_preparedState->pathFoundElement.getType() != Array) {
- mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id");
- return Status(
- ErrorCodes::BadValue,
- str::stream() << "Can only apply $pullAll to an array. " << idElem.toString()
- << " has the field "
- << _preparedState->pathFoundElement.getFieldName()
- << " of non-array type "
- << typeName(_preparedState->pathFoundElement.getType()));
- }
-
- // No children, nothing to do -- not an error state
- if (!_preparedState->pathFoundElement.hasChildren()) {
- execInfo->noOp = true;
- } else {
- mutablebson::Element elem = _preparedState->pathFoundElement.leftChild();
- while (elem.ok()) {
- if (std::find_if(_elementsToFind.begin(),
- _elementsToFind.end(),
- mutableElementEqualsBSONElement(elem, _collator)) !=
- _elementsToFind.end()) {
- _preparedState->elementsToRemove.push_back(elem);
- }
- elem = elem.rightSibling();
- }
-
- // Nothing to remove so it is a noOp.
- if (_preparedState->elementsToRemove.empty())
- execInfo->noOp = true;
- }
- }
- } else {
- // Let the caller know we can't do anything given the mod, _fieldRef, and doc.
- execInfo->noOp = true;
-
-
- // okay if path not found
- if (status.code() == ErrorCodes::NonExistentPath)
- status = Status::OK();
- }
-
- // Let the caller know what field we care about
- execInfo->fieldRef[0] = &_fieldRef;
-
- return status;
-}
-
-Status ModifierPullAll::apply() const {
- _preparedState->applyCalled = true;
-
- vector<mutablebson::Element>::const_iterator curr = _preparedState->elementsToRemove.begin();
- const vector<mutablebson::Element>::const_iterator end = _preparedState->elementsToRemove.end();
- for (; curr != end; ++curr) {
- const_cast<mutablebson::Element&>(*curr).remove().transitional_ignore();
- }
- return Status::OK();
-}
-
-Status ModifierPullAll::log(LogBuilder* logBuilder) const {
- // log document
- mutablebson::Document& doc = logBuilder->getDocument();
- const bool pathExists = _preparedState->pathFoundElement.ok() &&
- (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1));
-
- if (!pathExists)
- return logBuilder->addToUnsets(_fieldRef.dottedField());
-
- // value for the logElement ("field.path.name": <value>)
- mutablebson::Element logElement =
- doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _preparedState->pathFoundElement);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Could not append entry to $pullAll oplog entry: "
- << "set '"
- << _fieldRef.dottedField()
- << "' -> "
- << _preparedState->pathFoundElement.toString());
- }
- return logBuilder->addToSets(logElement);
-}
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull_all.h b/src/mongo/db/ops/modifier_pull_all.h
deleted file mode 100644
index 4cb762bd385..00000000000
--- a/src/mongo/db/ops/modifier_pull_all.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-class ModifierPullAll : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPullAll);
-
-public:
- ModifierPullAll();
- virtual ~ModifierPullAll();
-
- /**
- * The modifier $pullAll takes an array of values to match literally, and remove
- *
- * Ex. {$pullAll : {<field> : [<values>]}}
- * {$pullAll :{ array : [1,2] } } will transform {array: [1,2,3]} -> {array: [3]}
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
-
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- virtual Status apply() const;
-
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator) {
- invariant(!_collator);
- _collator = collator;
- }
-
-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 _positionalPathIndex;
-
- // The instance of the field in the provided doc.
- // This data is valid after prepare, for use by log and apply
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-
- // User specified elements to remove
- std::vector<BSONElement> _elementsToFind;
-
- // The collator this modifier uses when comparing strings.
- const CollatorInterface* _collator = nullptr;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_pull_all_test.cpp b/src/mongo/db/ops/modifier_pull_all_test.cpp
deleted file mode 100644
index 8454e5ff616..00000000000
--- a/src/mongo/db/ops/modifier_pull_all_test.cpp
+++ /dev/null
@@ -1,285 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_pull_all.h"
-
-#include <cstdint>
-
-#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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::CollatorInterfaceMock;
-using mongo::ExpressionContextForTest;
-using mongo::LogBuilder;
-using mongo::ModifierPullAll;
-using mongo::ModifierInterface;
-using mongo::NumberInt;
-using mongo::Status;
-using mongo::StringData;
-using mongo::fromjson;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate the mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj,
- ModifierInterface::Options options =
- ModifierInterface::Options::normal(new ExpressionContextForTest()))
- : _modObj(modObj), _mod() {
- ASSERT_OK(_mod.init(_modObj["$pullAll"].embeddedObject().firstElement(), options));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierPullAll& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierPullAll _mod;
-};
-
-TEST(Init, BadThings) {
- BSONObj modObj;
- ModifierPullAll mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- modObj = fromjson("{$pullAll: {a:1}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{$pullAll: {a:'test'}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{$pullAll: {a:{}}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-
- modObj = fromjson("{$pullAll: {a:true}}");
- ASSERT_NOT_OK(mod.init(modObj["$pullAll"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(PrepareApply, SimpleNumber) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : ['a', {r:1, b:2}] }"), doc);
-}
-
-TEST(PrepareApply, MissingElement) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : ['r'] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [1, 'a', {r:1, b:2}] }"), doc);
-}
-
-TEST(PrepareApply, TwoElements) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [1, 'a'] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [{r:1, b:2}] }"), doc);
-}
-
-TEST(EmptyResult, RemoveEverythingOutOfOrder) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : {a : [ {r:1, b:2}, 1, 'a' ] }}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(EmptyResult, RemoveEverythingInOrder) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [1, 'a', {r:1, b:2} ] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(EmptyResult, RemoveEverythingAndThenSome) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [2,3,1,'r', {r:1, b:2}, 'a' ] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(PrepareLog, MissingPullValue) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { a : [2] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [1, 'a', {r:1, b:2}] } }"), logDoc);
-}
-
-TEST(PrepareLog, MissingPath) {
- Document doc(fromjson("{ a : [1, 'a', {r:1, b:2}] }"));
- Mod mod(fromjson("{ $pullAll : { b : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { b : true } }"), logDoc);
-}
-
-TEST(Prepare, MissingArrayElementPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pullAll : { 'a.2' : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(Prepare, FromArrayElementPath) {
- Document doc(fromjson("{ a : [1, 2] }"));
- Mod mod(fromjson("{ $pullAll : { 'a.0' : [1] } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-
-TEST(Collation, RespectsCollationFromOptions) {
- Document doc(fromjson("{ a : ['foo', 'bar' ] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString);
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- expCtx->setCollator(&collator);
- Mod mod(fromjson("{ $pullAll : { 'a' : ['FOO', 'BAR'] } }"),
- ModifierInterface::Options::normal(expCtx));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{ a : [] }"));
-}
-
-TEST(Collation, RespectsCollationFromSetCollation) {
- Document doc(fromjson("{ a : ['foo', 'bar' ] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString);
- Mod mod(fromjson("{ $pullAll : { 'a' : ['FOO', 'BAR'] } }"));
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_EQUALS(doc, fromjson("{ a : [] }"));
-}
-} // namespace
diff --git a/src/mongo/db/ops/modifier_pull_test.cpp b/src/mongo/db/ops/modifier_pull_test.cpp
deleted file mode 100644
index e21e1648e0c..00000000000
--- a/src/mongo/db/ops/modifier_pull_test.cpp
+++ /dev/null
@@ -1,776 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_pull.h"
-
-#include <cstdint>
-
-#include "mongo/base/string_data.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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-
-namespace {
-
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate a $pull mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) : _modObj(modObj), _mod() {
- ASSERT_OK(_mod.init(_modObj["$pull"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Mod(BSONObj modObj, const CollatorInterface* collator) : _modObj(modObj), _mod() {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- expCtx->setCollator(collator);
- ASSERT_OK(_mod.init(_modObj["$pull"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(std::move(expCtx))));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierPull& mod() {
- return _mod;
- }
-
-private:
- BSONObj _modObj;
- ModifierPull _mod;
-};
-
-TEST(SimpleMod, InitWithTextFails) {
- auto update = fromjson("{$pull: {a: {$text: {$search: 'str'}}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::BadValue, status);
-}
-
-TEST(SimpleMod, InitWithWhereFails) {
- auto update = fromjson("{$pull: {a: {$where: 'this.a == this.b'}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::BadValue, status);
-}
-
-TEST(SimpleMod, InitWithGeoNearElemFails) {
- auto update =
- fromjson("{$pull: {a: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::BadValue, status);
-}
-
-TEST(SimpleMod, InitWithGeoNearObjectFails) {
- auto update = fromjson(
- "{$pull: {a: {b: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::BadValue, status);
-}
-
-TEST(SimpleMod, InitWithExprElemFails) {
- auto update = fromjson("{$pull: {a: {$expr: {$eq: ['$a', 5]}}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::QueryFeatureNotAllowed, status);
-}
-
-TEST(SimpleMod, InitWithExprObjectFails) {
- auto update = fromjson("{$pull: {a: {$expr: {$eq: ['$a', {$literal: {b: 5}}]}}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::QueryFeatureNotAllowed, status);
-}
-
-TEST(SimpleMod, InitWithJSONSchemaFails) {
- auto update = fromjson("{$pull: {a: {$jsonSchema: {}}}}");
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ModifierPull node;
- auto status = node.init(update["$pull"]["a"], ModifierInterface::Options::normal(expCtx));
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::QueryFeatureNotAllowed, status);
-}
-
-TEST(SimpleMod, PrepareOKTargetNotFound) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { a : true } }"), logDoc);
-}
-
-TEST(SimpleMod, PrepareOKTargetFound) {
- Document doc(fromjson("{ a : [ 0, 1, 2, 3 ] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareInvalidTargetString) {
- Document doc(fromjson("{ a : 'foo' }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, PrepareInvalidTargetObject) {
- Document doc(fromjson("{ a : { 'foo' : 'bar' } }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(mod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(SimpleMod, PrepareAndLogEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { a : true } }"), logDoc);
-}
-
-TEST(SimpleMod, PrepareAndLogMissingElementAfterFoundPath) {
- Document doc(fromjson("{ a : { b : { c : {} } } }"));
- Mod mod(fromjson("{ $pull : { 'a.b.c.d' : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b.c.d");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $unset : { 'a.b.c.d' : true } }"), logDoc);
-}
-
-TEST(SimpleMod, PrepareAndLogEmptyArray) {
- Document doc(fromjson("{ a : [] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(SimpleMod, PullMatchesNone) {
- Document doc(fromjson("{ a : [2, 3, 4, 5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [2, 3, 4, 5] } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogPullMatchesOne) {
- Document doc(fromjson("{ a : [0, 1, 2, 3, 4, 5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3, 4, 5 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [1, 2, 3, 4, 5] } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogPullMatchesSeveral) {
- Document doc(fromjson("{ a : [0, 1, 0, 2, 0, 3, 0, 4, 0, 5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [ 1, 2, 3, 4, 5 ] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [1, 2, 3, 4, 5] } }"), logDoc);
-}
-
-TEST(SimpleMod, ApplyAndLogPullMatchesAll) {
- Document doc(fromjson("{ a : [0, -1, -2, -3, -4, -5] }"));
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(SimpleMod, PullRespectsTheCollation) {
- Document doc(fromjson("{ a : ['zaa', 'zcc', 'zbb', 'zee'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- Mod mod(fromjson("{ $pull : { a : { $gt : 'abc' } } }"), &collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : ['zaa', 'zbb'] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : ['zaa', 'zbb'] } }"), logDoc);
-}
-
-TEST(SimpleMod, CollationHasNoAffectWhenPullingNonStrings) {
- Document doc(fromjson("{ a : [0, -1, -2, -3, -4, -5] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- Mod mod(fromjson("{ $pull : { a : { $lt : 1 } } }"), &collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(SimpleMod, CollationHasNoAffectWhenPullingStringsUsingRegex) {
- Document doc(fromjson("{ a : ['b', 'a', 'aab', 'cb', 'bba'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- Mod mod(fromjson("{ $pull : { a : /a/ } }"), &collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : ['b', 'cb'] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : ['b', 'cb'] } }"), logDoc);
-}
-
-TEST(SimpleMod, PullingBasedOnStringLiteralRespectsCollation) {
- Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- Mod mod(fromjson("{ $pull : { a : 'c' } }"), &collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : [] } }"), logDoc);
-}
-
-TEST(SimpleMod, PullingBasedOnNumberLiteralNotAffectedByCollation) {
- Document doc(fromjson("{ a : ['a', 99, 'b', 2, 'c', 99, 'd'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- Mod mod(fromjson("{ $pull : { a : 99 } }"), &collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : ['a', 'b', 2, 'c', 'd'] }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{ $set : { a : ['a', 'b', 2, 'c', 'd'] } }"), logDoc);
-}
-
-TEST(SimpleMod, PullingBasedOnStringRespectsCollationProvidedBySetCollation) {
- Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- Mod mod(fromjson("{ $pull : { a : 'c' } }"), nullptr);
- mod.mod().setCollator(&collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ a : [] }"), doc);
-}
-
-TEST(ComplexMod, ApplyAndLogComplexDocAndMatching1) {
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
-
- // Modifier:
- "{ $pull : { 'a.b' : { $or : [ "
- " { 'y' : { $exists : true } }, "
- " { 'z' : { $exists : true } } "
- "] } } }",
-
- // Document result:
- "{ a : { b : [ { x : 1 }, { x : 2 } ] } }",
-
- // Log result:
- "{ $set : { 'a.b' : [ { x : 1 }, { x : 2 } ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(ComplexMod, ApplyAndLogComplexDocAndMatching2) {
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
-
- // Modifier:
- "{ $pull : { 'a.b' : { 'y' : { $exists : true } } } }",
-
- // Document result:
- "{ a : { b : [ { x : 1 }, { x : 2 }, { z : 'z' } ] } }",
-
- // Log result:
- "{ $set : { 'a.b' : [ { x : 1 }, { x : 2 }, { z : 'z' } ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(ComplexMod, ApplyAndLogComplexDocAndMatching3) {
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 1 }, { y : 'y' }, { x : 2 }, { z : 'z' } ] } }",
-
- // Modifier:
- "{ $pull : { 'a.b' : { $in : [ { x : 1 }, { y : 'y' } ] } } }",
-
- // Document result:
- "{ a : { b : [ { x : 2 }, { z : 'z' } ] } }",
-
- // Log result:
- "{ $set : { 'a.b' : [ { x : 2 }, { z : 'z' } ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(ComplexMod, FullPredicateInsidePullRespectsCollation) {
- const char* const strings[] = {
- // Document:
- "{ a : { b : [ { x : 'foo', y : 1 }, { x : 'bar', y : 2 }, { x : 'baz', y : 3 } ] } }",
-
- // Modifier:
- "{ $pull : { 'a.b' : { x : 'blah' } } }",
-
- // Document result:
- "{ a : { b : [ ] } }",
-
- // Log result:
- "{ $set : { 'a.b' : [ ] } }"};
-
- Document doc(fromjson(strings[0]));
-
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- Mod mod(fromjson(strings[1]), &collator);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(ValueMod, ApplyAndLogScalarValueMod) {
- const char* const strings[] = {// Document:
- "{ a : [1, 2, 1, 2, 1, 2] }",
-
- // Modifier:
- "{ $pull : { a : 1 } }",
-
- // Document result:
- "{ a : [ 2, 2, 2] }",
-
- // Log result:
- "{ $set : { a : [ 2, 2, 2 ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(ValueMod, ApplyAndLogObjectValueMod) {
- const char* const strings[] = {// Document:
- "{ a : [ { x : 1 }, { y : 2 }, { x : 1 }, { y : 2 } ] }",
-
- // Modifier:
- "{ $pull : { a : { y : 2 } } }",
-
- // Document result:
- "{ a : [ { x : 1 }, { x : 1 }] }",
-
- // Log result:
- "{ $set : { a : [ { x : 1 }, { x : 1 } ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(DocumentationTests, Example1) {
- const char* const strings[] = {
- // Document:
- "{ flags: ['vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce' ] }",
-
- // Modifier:
- "{ $pull: { flags: 'msr' } }",
-
- // Document result:
- "{ flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce' ] }",
-
- // Log result:
- "{ $set : { flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce' ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "flags");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(DocumentationTests, Example2a) {
- const char* const strings[] = {// Document:
- "{ votes: [ 3, 5, 6, 7, 7, 8 ] }",
-
- // Modifier:
- "{ $pull: { votes: 7 } }",
-
- // Document result:
- "{ votes: [ 3, 5, 6, 8 ] }",
-
- // Log result:
- "{ $set : { votes: [ 3, 5, 6, 8 ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "votes");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(DocumentationTests, Example2b) {
- const char* const strings[] = {// Document:
- "{ votes: [ 3, 5, 6, 7, 7, 8 ] }",
-
- // Modifier:
- "{ $pull: { votes: { $gt: 6 } } }",
-
- // Document result:
- "{ votes: [ 3, 5, 6 ] }",
-
- // Log result:
- "{ $set : { votes: [ 3, 5, 6 ] } }"};
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "votes");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(MatchingEdgeCases, NonObjectShortCircuit) {
- const char* const strings[] = {
- "{ a: [ { x: 1 }, 2 ] }",
-
- "{ $pull: { a: { x: 1 } } }",
-
- "{ a: [ 2 ] }",
-
- "{ $set : { a: [ 2 ] } }",
- };
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-TEST(MatchingRegressions, SERVER_3988) {
- const char* const strings[] = {
- "{ x: 1, y: [ 2, 3, 4, 'abc', 'xyz' ] }",
-
- "{ $pull: { y: /yz/ } }",
-
- "{ x: 1, y: [ 2, 3, 4, 'abc' ] }",
-
- "{ $set : { y: [ 2, 3, 4, 'abc' ] } }",
- };
-
- Document doc(fromjson(strings[0]));
- Mod mod(fromjson(strings[1]));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "y");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson(strings[2]), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod.log(&logBuilder));
- ASSERT_EQUALS(fromjson(strings[3]), logDoc);
-}
-
-} // namespace
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_push.cpp b/src/mongo/db/ops/modifier_push.cpp
deleted file mode 100644
index 65b7cad0bbc..00000000000
--- a/src/mongo/db/ops/modifier_push.cpp
+++ /dev/null
@@ -1,660 +0,0 @@
-/**
- * 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/>.
- *
- * 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.
- */
-
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
-
-#include "mongo/db/ops/modifier_push.h"
-
-#include <algorithm>
-#include <cmath>
-#include <limits>
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/assert_util.h"
-#include "mongo/util/log.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-using std::abs;
-using std::numeric_limits;
-
-namespace mb = mutablebson;
-namespace str = mongoutils::str;
-
-namespace {
-
-const char kEach[] = "$each";
-const char kSlice[] = "$slice";
-const char kSort[] = "$sort";
-const char kPosition[] = "$position";
-
-bool isPatternElement(const BSONElement& pattern) {
- if (!pattern.isNumber()) {
- return false;
- }
-
- // Patterns can be only 1 or -1.
- double val = pattern.Number();
- if (val != 1 && val != -1) {
- return false;
- }
-
- return true;
-}
-
-bool inEachMode(const BSONElement& modExpr) {
- if (modExpr.type() != Object) {
- return false;
- }
- BSONObj obj = modExpr.embeddedObject();
- if (obj[kEach].type() == EOO) {
- return false;
- }
- return true;
-}
-
-Status parseEachMode(const BSONElement& modExpr,
- BSONElement* eachElem,
- BSONElement* sliceElem,
- BSONElement* sortElem,
- BSONElement* positionElem) {
- Status status = Status::OK();
-
- // The $each clause must be an array.
- *eachElem = modExpr.embeddedObject()[kEach];
- if (eachElem->type() != Array) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The argument to $each in $push must be"
- " an array but it was of type: "
- << typeName(eachElem->type()));
- }
-
- // There must be only one $each clause.
- bool seenEach = false;
- BSONObjIterator itMod(modExpr.embeddedObject());
- while (itMod.more()) {
- BSONElement modElem = itMod.next();
- if (mongoutils::str::equals(modElem.fieldName(), kEach)) {
- if (seenEach) {
- return Status(ErrorCodes::BadValue, "Only one $each clause is supported.");
- }
- seenEach = true;
- }
- }
-
- // Slice, sort, position are optional and may be present in any order.
- bool seenSlice = false;
- bool seenSort = false;
- bool seenPosition = false;
- BSONObjIterator itPush(modExpr.embeddedObject());
- while (itPush.more()) {
- BSONElement elem = itPush.next();
- if (mongoutils::str::equals(elem.fieldName(), kSlice)) {
- if (seenSlice) {
- return Status(ErrorCodes::BadValue, "Only one $slice clause is supported.");
- }
- *sliceElem = elem;
- seenSlice = true;
- } else if (mongoutils::str::equals(elem.fieldName(), kSort)) {
- if (seenSort) {
- return Status(ErrorCodes::BadValue, "Only one $sort clause is supported.");
- }
- *sortElem = elem;
- seenSort = true;
- } else if (mongoutils::str::equals(elem.fieldName(), kPosition)) {
- if (seenPosition) {
- return Status(ErrorCodes::BadValue, "Only one $position clause is supported.");
- }
- *positionElem = elem;
- seenPosition = true;
- } else if (!mongoutils::str::equals(elem.fieldName(), kEach)) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Unrecognized clause in $push: "
- << elem.fieldNameStringData());
- }
- }
-
- return Status::OK();
-}
-
-} // unnamed namespace
-
-struct ModifierPush::PreparedState {
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc), idxFound(0), elemFound(doc.end()), arrayPreModSize(0) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-
- // The size of the array before the push.
- size_t arrayPreModSize;
-
- // The actual position at which to push the elements, in range [0, arrayPreModSize].
- size_t actualPosition;
-};
-
-ModifierPush::ModifierPush()
- : _fieldRef(),
- _posDollar(0),
- _eachMode(false),
- _eachElem(),
- _slicePresent(false),
- _slice(0),
- _sortPresent(false),
- _position(std::numeric_limits<std::int32_t>::max()),
- _sort(),
- _val() {}
-
-ModifierPush::~ModifierPush() {}
-
-Status ModifierPush::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- //
- // field name analysis
- //
-
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // the field is fit for updates.
- _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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- //
- // value analysis
- //
-
- // Are the target push values safe to store?
- BSONElement sliceElem;
- BSONElement sortElem;
- BSONElement positionElem;
- switch (modExpr.type()) {
- case Object:
- // If any known clause ($each, $slice, or $sort) is present, we'd assume
- // we're using the $each variation of push and would parse accodingly.
- _eachMode = inEachMode(modExpr);
- if (_eachMode) {
- Status status =
- parseEachMode(modExpr, &_eachElem, &sliceElem, &sortElem, &positionElem);
- if (!status.isOK()) {
- return status;
- }
- } else {
- _val = modExpr;
- }
- break;
-
- default:
- _val = modExpr;
- break;
- }
-
- // Is slice present and correct?
- if (sliceElem.type() != EOO) {
- if (!sliceElem.isNumber()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The value for $slice must "
- "be a numeric value but was given type: "
- << typeName(sliceElem.type()));
- }
-
- // TODO: Cleanup and unify numbers wrt getting int32/64 bson values (from doubles)
-
- // If the value of slice is not fraction, even if it's a double, we allow it. The
- // reason here is that the shell will use doubles by default unless told otherwise.
- const double doubleVal = sliceElem.numberDouble();
- if (doubleVal - static_cast<int64_t>(doubleVal) != 0) {
- return Status(ErrorCodes::BadValue, "The $slice value in $push cannot be fractional");
- }
-
- _slice = sliceElem.numberLong();
- _slicePresent = true;
- }
-
- // Is position present and correct?
- if (positionElem.type() != EOO) {
- // Check that $position can be represented by a 32-bit integer.
- switch (positionElem.type()) {
- case NumberInt:
- break;
- case NumberLong:
- if (positionElem.numberInt() != positionElem.numberLong()) {
- return Status(
- ErrorCodes::BadValue,
- "The $position value in $push must be representable as a 32-bit integer.");
- }
- break;
- case NumberDouble: {
- const auto doubleVal = positionElem.numberDouble();
- if (doubleVal != 0.0) {
- if (!std::isnormal(doubleVal) || (doubleVal != positionElem.numberInt())) {
- return Status(ErrorCodes::BadValue,
- "The $position value in $push must be representable as a "
- "32-bit integer.");
- }
- }
- break;
- }
- default:
- return Status(ErrorCodes::BadValue,
- str::stream() << "The value for $position must "
- "be a non-negative numeric value, not of type: "
- << typeName(positionElem.type()));
- }
-
- _position = positionElem.numberInt();
- }
-
- // Is sort present and correct?
- if (sortElem.type() != EOO) {
- if (sortElem.type() != Object && !sortElem.isNumber()) {
- return Status(ErrorCodes::BadValue,
- "The $sort is invalid: use 1/-1 to sort the whole element, "
- "or {field:1/-1} to sort embedded fields");
- }
-
- if (sortElem.isABSONObj()) {
- BSONObj sortObj = sortElem.embeddedObject();
- if (sortObj.isEmpty()) {
- return Status(ErrorCodes::BadValue,
- "The $sort pattern is empty when it should be a set of fields.");
- }
-
- // Check if the sort pattern is sound.
- BSONObjIterator sortIter(sortObj);
- while (sortIter.more()) {
- BSONElement sortPatternElem = sortIter.next();
-
- // We require either <field>: 1 or -1 for asc and desc.
- if (!isPatternElement(sortPatternElem)) {
- return Status(ErrorCodes::BadValue,
- "The $sort element value must be either 1 or -1");
- }
-
- // All fields parts must be valid.
- FieldRef sortField(sortPatternElem.fieldName());
- if (sortField.numParts() == 0) {
- return Status(ErrorCodes::BadValue, "The $sort field cannot be empty");
- }
-
- for (size_t i = 0; i < sortField.numParts(); i++) {
- if (sortField.getPart(i).size() == 0) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The $sort field is a dotted field "
- "but has an empty part: "
- << sortField.dottedField());
- }
- }
- }
-
- _sort = PatternElementCmp(sortElem.embeddedObject(), opts.expCtx->getCollator());
- } else {
- // Ensure the sortElem number is valid.
- if (!isPatternElement(sortElem)) {
- return Status(ErrorCodes::BadValue,
- "The $sort element value must be either 1 or -1");
- }
-
- _sort = PatternElementCmp(BSON("" << sortElem.number()), opts.expCtx->getCollator());
- }
-
- _sortPresent = true;
- }
-
- return Status::OK();
-}
-
-void ModifierPush::setCollator(const CollatorInterface* collator) {
- invariant(!_sort.collator);
- if (_sortPresent) {
- _sort.collator = collator;
- }
-}
-
-Status ModifierPush::prepare(mutablebson::Element root,
- 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // 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 remainin path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
- // 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()) {
- const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts() - 1));
- // If the path exists, we require the target field to be already an
- // array.
- if (destExists && _preparedState->elemFound.getType() != Array) {
- mb::Element idElem = mb::findFirstChildNamed(root, "_id");
- return Status(ErrorCodes::BadValue,
- str::stream() << "The field '" << _fieldRef.dottedField() << "'"
- << " must be an array but is of type "
- << typeName(_preparedState->elemFound.getType())
- << " in document {"
- << idElem.toString()
- << "}");
- }
- } else {
- 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;
-
- if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
- }
-
- return Status::OK();
-}
-
-namespace {
-
-/**
- * Add 'elem' at index 'pos' in 'arrayElem'. 'arrayElem' should be an array of size 'arraySize', and
- * 'pos' should be in the range [0, 'arraySize'].
- */
-Status pushFirstElement(mb::Element& arrayElem,
- const size_t arraySize,
- const size_t pos,
- mb::Element& elem) {
- // Empty array or pushing to the front
- if (arraySize == 0 || pos == 0) {
- return arrayElem.pushFront(elem);
- } else {
- // Push position is at the end.
- if (pos == arraySize) {
- return arrayElem.pushBack(elem);
- }
-
- const size_t appendPos = pos - 1;
- mutablebson::Element fromElem = getNthChild(arrayElem, appendPos);
-
- // Error if pos > arraySize.
- if (!fromElem.ok()) {
- return Status(ErrorCodes::InvalidLength,
- str::stream() << "The specified position (" << appendPos << "/" << pos
- << ") is invalid based on the length ( "
- << arraySize
- << ") of the array");
- }
-
- return fromElem.addSiblingRight(elem);
- }
-}
-} // unamed namespace
-
-Status ModifierPush::apply() const {
- Status status = Status::OK();
-
- //
- // Applying a $push with an $clause has the following steps
- // 1. Create the doc array we'll push into, if it is not there
- // 2. Add the items in the $each array (or the simple $push) to the doc array
- // 3. Sort the resulting array according to $sort clause, if present
- // 4. Trim the resulting array according the $slice clause, if present
- //
- // TODO There are _lots_ of optimization opportunities that we'll consider once the
- // test coverage is adequate.
- //
-
- // 1. If the array field is not there, create it as an array and attach it to the
- // document.
- if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // Creates the array element
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1);
- mutablebson::Element baseArray = doc.makeElementArray(lastPart);
- if (!baseArray.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new base array");
- }
-
- // 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.
- status = pathsupport::createPathAt(
- _fieldRef, _preparedState->idxFound, _preparedState->elemFound, baseArray)
- .getStatus();
- if (!status.isOK()) {
- return status;
- }
-
- // Point to the base array just created. The subsequent code expects it to exist
- // already.
- _preparedState->elemFound = baseArray;
- }
-
- // This is the count of the array before we change it, or 0 if missing from the doc.
- _preparedState->arrayPreModSize = countChildren(_preparedState->elemFound);
-
- // Compute the actual position at which to push the elements.
- int32_t actualPosition = _position;
- // Negative positions are subtracted from the length of the array.
- if (actualPosition < 0) {
- actualPosition = _preparedState->arrayPreModSize + _position;
- }
- // Default to adding to the end of the array if the position was too high.
- if (actualPosition > int32_t(_preparedState->arrayPreModSize)) {
- actualPosition = _preparedState->arrayPreModSize;
- }
- // Default to adding to the beginning of the array if the position was too low.
- if (actualPosition < 0) {
- actualPosition = 0;
- }
- _preparedState->actualPosition = actualPosition;
-
- // 2. Add new elements to the array either by going over the $each array or by
- // appending the (old style $push) element.
- if (_eachMode) {
- BSONObjIterator itEach(_eachElem.embeddedObject());
-
- // When adding more than one element we keep track of the previous one
- // so we can add right siblings to it.
- mutablebson::Element prevElem = _preparedState->doc.end();
-
- // The first element is special below
- bool first = true;
-
- while (itEach.more()) {
- BSONElement eachItem = itEach.next();
- mutablebson::Element elem =
- _preparedState->doc.makeElementWithNewFieldName(StringData(), eachItem);
-
- if (first) {
- status = pushFirstElement(_preparedState->elemFound,
- _preparedState->arrayPreModSize,
- _preparedState->actualPosition,
- elem);
- } else {
- status = prevElem.addSiblingRight(elem);
- }
-
- if (!status.isOK()) {
- return status;
- }
-
- // For the next iteration the previous element will be the left sibling
- prevElem = elem;
- first = false;
- }
- } else {
- mutablebson::Element elem =
- _preparedState->doc.makeElementWithNewFieldName(StringData(), _val);
- if (!elem.ok()) {
- return Status(ErrorCodes::InternalError, "can't wrap element being $push-ed");
- }
- return pushFirstElement(_preparedState->elemFound,
- _preparedState->arrayPreModSize,
- _preparedState->actualPosition,
- elem);
- }
-
- // 3. Sort the resulting array, if $sort was requested.
- if (_sortPresent) {
- sortChildren(_preparedState->elemFound, _sort);
- }
-
- // 4. Trim the resulting array according to $slice, if present.
- if (_slicePresent) {
- // Slice 0 means to remove all
- if (_slice == 0) {
- while (_preparedState->elemFound.ok() && _preparedState->elemFound.rightChild().ok()) {
- _preparedState->elemFound.rightChild().remove().transitional_ignore();
- }
- }
-
- const int64_t numChildren = mutablebson::countChildren(_preparedState->elemFound);
- int64_t countRemoved = std::max(static_cast<int64_t>(0), numChildren - abs(_slice));
-
- // If _slice is negative, remove from the bottom, otherwise from the top
- const bool removeFromEnd = (_slice > 0);
-
- // Either start at right or left depending if we are taking from top or bottom
- mutablebson::Element curr = removeFromEnd ? _preparedState->elemFound.rightChild()
- : _preparedState->elemFound.leftChild();
- while (curr.ok() && countRemoved > 0) {
- mutablebson::Element toRemove = curr;
- // Either go right or left depending if we are taking from top or bottom
- curr = removeFromEnd ? curr.leftSibling() : curr.rightSibling();
-
- status = toRemove.remove();
- if (!status.isOK()) {
- return status;
- }
- countRemoved--;
- }
- }
-
- return status;
-}
-
-Status ModifierPush::log(LogBuilder* logBuilder) const {
- // The start position to use for positional (ordinal) updates to the array
- // (We will increment as we append elements to the oplog entry so can't be const)
- size_t position = _preparedState->arrayPreModSize;
-
- // NOTE: Idempotence Requirement
- // In the case that the document does't have an array or it is empty we need to make sure
- // that the first time the field gets filled with items that it is a full set of the array.
-
- // If we sorted, sliced, or added the first items to the array, make a full array copy.
- const bool doFullCopy = _slicePresent || _sortPresent ||
- (position == 0) // first element in new/empty array
- || (_preparedState->actualPosition < _preparedState->arrayPreModSize); // add in middle
-
- if (doFullCopy) {
- return logBuilder->addToSetsWithNewFieldName(_fieldRef.dottedField(),
- _preparedState->elemFound);
- } else {
- // Set only the positional elements appended
- if (_eachMode) {
- // For each input element log it as a posisional $set
- BSONObjIterator itEach(_eachElem.embeddedObject());
- while (itEach.more()) {
- BSONElement eachItem = itEach.next();
- // value for the logElement ("field.path.name.N": <value>)
- const std::string positionalName = mongoutils::str::stream()
- << _fieldRef.dottedField() << "." << position++;
-
- Status s = logBuilder->addToSetsWithNewFieldName(positionalName, eachItem);
- if (!s.isOK())
- return s;
- }
-
- return Status::OK();
- } else {
- // single value for the logElement ("field.path.name.N": <value>)
- const std::string positionalName = mongoutils::str::stream() << _fieldRef.dottedField()
- << "." << position++;
-
- return logBuilder->addToSetsWithNewFieldName(positionalName, _val);
- }
- }
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_push.h b/src/mongo/db/ops/modifier_push.h
deleted file mode 100644
index 2173bfb99d6..00000000000
--- a/src/mongo/db/ops/modifier_push.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-#include "mongo/db/update/push_sorter.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-class ModifierPush : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierPush);
-
-public:
- ModifierPush();
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierPush();
-
- /**
- * A 'modExpr' here is a BSONElement {<fieldname>: <each clause>, <slice clause>, <sort clause>,
- * <position clause>} coming from a $push mod such as {$set: {x: $each: [{a: 1}], $slice: 3,
- * $sort: {b: 1}, $position: 5}}. init() extracts and validates the field name and the clauses.
- * It returns OK if successful or a status describing the error.
- *
- * There are currently a few restrictions concerning the clauses (but all can be
- * lifted):
- * + $slice can be negative only (ie, slicing from the recent end)
- * + $sort requires $slice to be present
- * + $sort can only sort objects (as opposed to basic types), so it only takes
- * object as patterns
- * + Because of the previous, $sort requires that the array being pushed to be made
- * of objects
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
-
- /**
- * Locates the array to be pushed into in the 'root', if it exists, and fills in
- * execInfo accordingly. Returns true if $push would succeed in 'root', otherwise
- * return a status describing the error.
- *
- * Note that a $push is never in-place. The cost of checking if it is a no-op makes it
- * so that we don't do such check either. As such, execInfo is always filled with
- * 'false' for those two options.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /**
- * Pushes the array into the prepared position and "sort/slice"s the resulting array
- * according to that call's instructions.
- */
- virtual Status apply() const;
-
- /**
- * $push currently logs the entire resulting array as a $set.
- *
- * TODO Log a positional $set in the array, whenever possible.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator);
-
-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;
-
- // Clauses for the $push that are filled when the $each variation of the command is used.
- bool _eachMode;
- BSONElement _eachElem;
- bool _slicePresent;
- int64_t _slice;
- bool _sortPresent;
- int32_t _position; // Can be negative.
-
- PatternElementCmp _sort;
-
- // Simple (old style) push value when the $each variation of the command is not
- // used. The _eachMode flag would be off if we're this mode.
- BSONElement _val;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_push_test.cpp b/src/mongo/db/ops/modifier_push_test.cpp
deleted file mode 100644
index d461f992afb..00000000000
--- a/src/mongo/db/ops/modifier_push_test.cpp
+++ /dev/null
@@ -1,1544 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_push.h"
-
-#include <algorithm>
-#include <cstdint>
-#include <iostream>
-#include <vector>
-
-#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/bson/ordering.h"
-#include "mongo/db/bson/dotted_path_support.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/json.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::BSONObjBuilder;
-using mongo::BSONArrayBuilder;
-using mongo::CollatorInterfaceMock;
-using mongo::ExpressionContextForTest;
-using mongo::fromjson;
-using mongo::LogBuilder;
-using mongo::ModifierInterface;
-using mongo::ModifierPush;
-using mongo::NumberInt;
-using mongo::Ordering;
-using mongo::Status;
-using mongo::StringData;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::countChildren;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-using std::sort;
-using std::vector;
-
-namespace dps = ::mongo::dotted_path_support;
-
-void combineVec(const vector<int>& origVec,
- const vector<int>& modVec,
- int32_t slice,
- vector<int>* combined) {
- using namespace std;
- combined->clear();
-
- // Slice 0 means the result is empty
- if (slice == 0)
- return;
-
- // Combine both vectors
- *combined = origVec;
- combined->insert(combined->end(), modVec.begin(), modVec.end());
-
- // Remove sliced items
- bool removeFromFront = (slice < 0);
-
- // if abs(slice) is larger than the size, nothing to do.
- if (abs(slice) >= int32_t(combined->size()))
- return;
-
- if (removeFromFront) {
- // Slice is negative.
- int32_t removeCount = combined->size() + slice;
- combined->erase(combined->begin(), combined->begin() + removeCount);
- } else {
- combined->resize(std::min(combined->size(), size_t(slice)));
- }
-}
-
-/**
- * Comparator between two BSONObjects that takes in consideration only the keys and
- * direction described in the sort pattern.
- */
-struct ProjectKeyCmp {
- BSONObj sortPattern;
- bool useWholeValue;
-
- ProjectKeyCmp(BSONObj pattern) : sortPattern(pattern) {
- useWholeValue = pattern.hasField("");
- }
-
- int operator()(const BSONObj& left, const BSONObj& right) const {
- int ret = 0;
- if (useWholeValue) {
- ret = left.woCompare(right, Ordering::make(sortPattern), false);
- } else {
- BSONObj lhsKey = dps::extractElementsBasedOnTemplate(left, sortPattern, true);
- BSONObj rhsKey = dps::extractElementsBasedOnTemplate(right, sortPattern, true);
- ret = lhsKey.woCompare(rhsKey, sortPattern);
- }
- return ret < 0;
- }
-};
-
-void combineAndSortVec(const vector<BSONObj>& origVec,
- const vector<BSONObj>& modVec,
- int32_t slice,
- BSONObj sortOrder,
- vector<BSONObj>* combined) {
- combined->clear();
-
- // Slice 0 means the result is empty
- if (slice == 0)
- return;
-
- *combined = origVec;
- combined->insert(combined->end(), modVec.begin(), modVec.end());
-
- sort(combined->begin(), combined->end(), ProjectKeyCmp(sortOrder));
-
- // Remove sliced items
- bool removeFromFront = (slice < 0);
-
- // if abs(slice) is larger than the size, nothing to do.
- if (abs(slice) >= int32_t(combined->size()))
- return;
-
- if (removeFromFront) {
- // Slice is negative.
- int32_t removeCount = combined->size() + slice;
- combined->erase(combined->begin(), combined->begin() + removeCount);
- } else {
- combined->resize(std::min(combined->size(), size_t(slice)));
- }
-}
-
-//
-// Init testing (module field checking, which is done in 'fieldchecker'
-//
-
-TEST(Init, SimplePush) {
- BSONObj modObj = fromjson("{$push: {x: 0}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-//
-// If present, is the $each clause valid?
-//
-
-TEST(Init, PushEachNormal) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2]}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachMixed) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, {a: 2}]}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachObject) {
- // $each must be an array
- BSONObj modObj = fromjson("{$push: {x: {$each: {'0': 1}}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachSimpleType) {
- // $each must be an array.
- BSONObj modObj = fromjson("{$push: {x: {$each: 1}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachEmpty) {
- BSONObj modObj = fromjson("{$push: {x: {$each: []}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachInvalidType) {
- // $each must be an array.
- BSONObj modObj = fromjson("{$push: {x: {$each: {b: 1}}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-//
-// If present, is the $slice clause valid?
-//
-
-TEST(Init, PushEachWithSliceBottom) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -3}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithSliceTop) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: 3}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithInvalidSliceObject) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: {a: 1}}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithInvalidSliceDouble) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -2.1}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithValidSliceDouble) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: -2.0}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithUnsupportedFullSlice) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: [1,2]}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithWrongTypeSlice) {
- BSONObj modObj = fromjson("{$push: {x: {$each: [1, 2], $slice: '-1'}}}");
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-//
-// If present, is the sort $sort clause valid?
-//
-
-TEST(Init, PushEachWithObjectSort) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithNumbericSort) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort:1 }}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithInvalidSortType) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: [{a:1}]}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachDuplicateSortPattern) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: [{a:1,a:1}]}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithInvalidSortValue) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:100}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithEmptySortField) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithEmptyDottedSortField) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'.':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithMissingSortFieldSuffix) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'a.':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithMissingSortFieldPreffix) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'.b':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithMissingSortFieldMiddle) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {'a..b':1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithEmptySort) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort:{} }}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-//
-// Are all clauses present? Is anything extroneous? Is anything duplicated?
-//
-
-TEST(Init, PushEachWithSortMissingSlice) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachInvalidClause) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $xxx: -1, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachExtraField) {
- const char* c = "{$push: {x: {$each: [{a:1},{a:2}], $slice: -2.0, $sort: {a:1}, b: 1}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachDuplicateSortClause) {
- const char* c = "{$push: {x:{$each:[{a:1},{a:2}], $slice:-2.0, $sort:{a:1}, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachDuplicateSliceClause) {
- const char* c = "{$push: {x: {$each:[{a:1},{a:2}], $slice:-2.0, $slice:-2, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachDuplicateEachClause) {
- const char* c = "{$push: {x: {$each:[{a:1}], $each:[{a:2}], $slice:-3, $sort:{a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithSliceFirst) {
- const char* c = "{$push: {x: {$slice: -2.0, $each: [{a:1},{a:2}], $sort: {a:1}}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(Init, PushEachWithSortFirst) {
- const char* c = "{$push: {x: {$sort: {a:1}, $slice: -2.0, $each: [{a:1},{a:2}]}}}";
- BSONObj modObj = fromjson(c);
- ModifierPush mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(mod.init(modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-//
-// Simple mod
-//
-
-/** Helper to build and manipulate a $push mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj,
- ModifierInterface::Options options =
- ModifierInterface::Options::normal(new ExpressionContextForTest()))
- : _mod() {
- _modObj = modObj;
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(_modObj[modName].embeddedObject().firstElement(), options));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierPush& mod() {
- return _mod;
- }
-
-private:
- ModifierPush _mod;
- BSONObj _modObj;
-};
-
-TEST(SimpleMod, PrepareNonArray) {
- Document doc(fromjson("{a: 1}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
-
- ModifierInterface::ExecInfo dummy;
- ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
-}
-
-TEST(SimpleMod, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
-}
-
-TEST(SimpleMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
-}
-
-TEST(SimpleMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
-}
-
-//
-// Simple object mod
-//
-
-TEST(SimpleObjMod, PrepareNonArray) {
- Document doc(fromjson("{a: 1}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
-
- ModifierInterface::ExecInfo dummy;
- ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
-}
-
-TEST(SimpleObjMod, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b:1}]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [{b:1}]}}"), logDoc);
-}
-
-TEST(SimpleObjMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b:1}]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [{b:1}]}}"), logDoc);
-}
-
-TEST(SimpleObjMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: [{b:0}]}"));
- Mod pushMod(fromjson("{$push: {a: {b: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b:0},{b:1}]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1':{b:1}}}"), logDoc);
-}
-
-TEST(SimpleObjMod, PrepareApplyDotted) {
- Document doc(
- fromjson("{ _id : 1 , "
- " question : 'a', "
- " choices : { "
- " first : { choice : 'b' }, "
- " second : { choice : 'c' } }"
- "}"));
- Mod pushMod(fromjson("{$push: {'choices.first.votes': 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "choices.first.votes");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ _id : 1 , "
- " question : 'a', "
- " choices : { "
- " first : { choice : 'b', votes: [1]}, "
- " second : { choice : 'c' } }"
- "}"),
- doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'choices.first.votes':[1]}}"), logDoc);
-}
-
-
-//
-// Simple $each mod
-//
-
-TEST(SimpleEachMod, PrepareNonArray) {
- Document doc(fromjson("{a: 1}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
-
- ModifierInterface::ExecInfo dummy;
- ASSERT_NOT_OK(pushMod.prepare(doc.root(), "", &dummy));
-}
-
-TEST(SimpleEachMod, PrepareApplyEmpty) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
-}
-
-TEST(SimpleEachMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1]}}"), logDoc);
-}
-
-TEST(SimpleEachMod, PrepareApplyInexistentMultiple) {
- Document doc(fromjson("{}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1, 2]}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [1, 2]}}"), logDoc);
-}
-
-TEST(SimpleEachMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1]}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1': 1}}"), logDoc);
-}
-
-TEST(SimpleEachMod, PrepareApplyNormalMultiple) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1,2]}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1,2]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1': 1, 'a.2':2}}"), logDoc);
-}
-
-/**
- * Slice variants
- */
-TEST(SlicePushEach, TopOne) {
- Document doc(fromjson("{a: [3]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [2, -1], $slice:1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [3]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [3]}}"), logDoc);
-}
-
-/**
- * Sort for scalar (whole) array elements
- */
-TEST(SortPushEach, NumberSort) {
- Document doc(fromjson("{a: [3]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [2, -1], $sort:1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [-1,2,3]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [-1, 2, 3]}}"), logDoc);
-}
-
-TEST(SortPushEach, NumberSortReverse) {
- Document doc(fromjson("{a: [3]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:-1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [4,3,-1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: [4,3,-1]}}"), logDoc);
-}
-
-TEST(SortPushEach, MixedSortWhole) {
- Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:1}}}"));
- const BSONObj expectedObj = fromjson("{a: [-1,3,4,'t', {a:1}, {b:1}]}");
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(expectedObj, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
-}
-
-TEST(SortPushEach, MixedSortWholeReverse) {
- Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:-1}}}"));
- const BSONObj expectedObj = fromjson("{a: [{b:1}, {a:1}, 't', 4, 3, -1]}");
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(expectedObj, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
-}
-
-TEST(SortPushEach, MixedSortEmbeddedField) {
- Document doc(fromjson("{a: [3, 't', {b:1}, {a:1}]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [4, -1], $sort:{a:1}}}}"));
- const BSONObj expectedObj = fromjson("{a: [3, 't', {b: 1}, 4, -1, {a: 1}]}");
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(expectedObj, doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << expectedObj), logDoc);
-}
-
-TEST(SortPushEach, SortRespectsCollationFromOptions) {
- Document doc(fromjson("{a: ['dd', 'fc', 'gb'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- expCtx->setCollator(&collator);
- Mod pushMod(fromjson("{$push: {a: {$each: ['ha'], $sort: 1}}}"),
- ModifierInterface::Options::normal(expCtx));
- const BSONObj expectedObj = fromjson("{a: ['ha', 'gb', 'fc', 'dd']}");
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_EQUALS(expectedObj, doc);
-}
-
-TEST(SortPushEach, SortRespectsCollationFromSetCollator) {
- Document doc(fromjson("{a: ['dd', 'fc', 'gb'] }"));
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- Mod pushMod(fromjson("{$push: {a: {$each: ['ha'], $sort: 1}}}"));
- pushMod.mod().setCollator(&collator);
- const BSONObj expectedObj = fromjson("{a: ['ha', 'gb', 'fc', 'dd']}");
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_EQUALS(expectedObj, doc);
-}
-
-/**
- * This fixture supports building $push mods with parameterized $each arrays and $slices.
- * It always assume that the array being operated on is called 'a'. To build a mod, one
- * issues a set*Mod() call.
- *
- * The setSimpleMod() call will build a $each array of numbers. The setObjectMod() call
- * will build a $each array with object. Both these calls take the slice as a parameter as
- * well.
- *
- * Here's a typical test case flow:
- * + Determine what the original document's 'a' array would contain
- * + Ditto for the $push's $each arrray
- * + Loop over slice value
- * + Apply the $push with current slice value to the doc
- * + Use the fixture/helpers to combine and slice the mod's and original's 'a'
- * array
- * + Build a document with the above and check against the one generated by the mod apply
- */
-class SlicedMod : public mongo::unittest::Test {
-public:
- SlicedMod() : _mod() {}
-
- virtual void setUp() {
- // no op; set all state using the setMod() call
- }
-
- /** Sets up the mod to be {$push: {a: {$each: [<eachArray>], $slice: <slice>}}} */
- void setSimpleMod(int32_t slice, const vector<int>& eachArray) {
- BSONArrayBuilder arrBuilder;
- for (vector<int>::const_iterator it = eachArray.begin(); it != eachArray.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- _modObj =
- BSON("$push" << BSON("a" << BSON("$each" << arrBuilder.arr() << "$slice" << slice)));
-
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(_mod.init(_modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- }
-
- /** Sets up the mod to be {$push: {a: {$each:[<Obj>,...], $slice:<slice>, $sort:<Obj>}}} */
- void setSortMod(int32_t slice, const vector<BSONObj>& eachArray, BSONObj sort) {
- BSONArrayBuilder arrBuilder;
- for (vector<BSONObj>::const_iterator it = eachArray.begin(); it != eachArray.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- _modObj = BSON(
- "$push" << BSON(
- "a" << BSON("$each" << arrBuilder.arr() << "$slice" << slice << "$sort" << sort)));
-
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_OK(_mod.init(_modObj["$push"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- }
-
- /** Returns an object {a: [<'vec's content>]} */
- BSONObj getObjectUsing(const vector<int>& vec) {
- BSONArrayBuilder arrBuilder;
- for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- BSONObjBuilder builder;
- builder.appendArray("a", arrBuilder.obj());
-
- return builder.obj();
- }
-
- /** Returns an object {a: [<'vec's content>]} */
- BSONObj getObjectUsing(const vector<BSONObj>& vec) {
- BSONArrayBuilder arrBuilder;
- for (vector<BSONObj>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
- arrBuilder.append(*it);
- }
-
- BSONObjBuilder builder;
- builder.appendArray("a", arrBuilder.obj());
-
- return builder.obj();
- }
-
- ModifierPush& mod() {
- return _mod;
- }
-
- BSONObj modObj() {
- return _modObj;
- }
-
-private:
- ModifierPush _mod;
- BSONObj _modObj;
- vector<int> _eachArray;
-};
-
-TEST_F(SlicedMod, SimpleArrayFromEmpty) {
- // We'll simulate the original document having {a: []} and the mod being
- // {$push: {a: {$each: [1], $slice: <-2..0>}}}
- vector<int> docArray;
- vector<int> eachArray;
- eachArray.push_back(1);
-
- for (int32_t slice = -2; slice <= 0; slice++) {
- setSimpleMod(slice, eachArray);
- Document doc(getObjectUsing(docArray /* {a: []} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<int> combinedVec;
- combineVec(docArray, /* a: [] */
- eachArray, /* a: [1] */
- slice,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
- }
-}
-
-TEST_F(SlicedMod, SimpleArrayFromExisting) {
- // We'll simulate the original document having {a: [2,3]} and the mod being
- // {$push: {a: {$each: [1], $slice: <-4..0>}}}
- vector<int> docArray;
- docArray.push_back(2);
- docArray.push_back(3);
- vector<int> eachArray;
- eachArray.push_back(1);
-
- for (int32_t slice = -4; slice <= 0; slice++) {
- setSimpleMod(slice, eachArray);
- Document doc(getObjectUsing(docArray /* {a: [2, 3]} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<int> combinedVec;
- combineVec(docArray, /* a: [2, 3] */
- eachArray, /* a: [1] */
- slice,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
- }
-}
-
-TEST_F(SlicedMod, ObjectArrayFromEmpty) {
- // We'll simulate the original document having {a: []} and the mod being
- // {$push: {a: {$each: [{a:2,b:1}], $slice: <-4..0>}, $sort: {a:-1/1,b:-1/1}}
- vector<BSONObj> docArray;
- vector<BSONObj> eachArray;
- eachArray.push_back(fromjson("{a:2,b:1}"));
- eachArray.push_back(fromjson("{a:1,b:2}"));
-
- for (int32_t aOrB = 0; aOrB < 2; aOrB++) {
- for (int32_t sortA = 0; sortA < 2; sortA++) {
- for (int32_t sortB = 0; sortB < 2; sortB++) {
- for (int32_t slice = -3; slice <= 3; slice++) {
- BSONObj sortOrder;
- if (aOrB == 0) {
- sortOrder = BSON("a" << (sortA ? 1 : -1) << "b" << (sortB ? 1 : -1));
- } else {
- sortOrder = BSON("b" << (sortB ? 1 : -1) << "a" << (sortA ? 1 : -1));
- }
-
- setSortMod(slice, eachArray, sortOrder);
- Document doc(getObjectUsing(docArray /* {a: []} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<BSONObj> combinedVec;
- combineAndSortVec(docArray, /* a: [] */
- eachArray, /* a: [{a:2,b:1},{a:1,b:2}] */
- slice,
- sortOrder,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod().log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << getObjectUsing(combinedVec)), logDoc);
- }
- }
- }
- }
-}
-
-TEST_F(SlicedMod, ObjectArrayFromExisting) {
- // We'll simulate the original document having {a: [{a:2,b:3},{a:3,:b1}]} and the mod being
- // {$push: {a: {$each: [{a:2,b:1}], $slice: <-4..0>}, $sort: {a:-1/1,b:-1/1}}
- vector<BSONObj> docArray;
- docArray.push_back(fromjson("{a:2,b:3}"));
- docArray.push_back(fromjson("{a:3,b:1}"));
- vector<BSONObj> eachArray;
- eachArray.push_back(fromjson("{a:2,b:1}"));
-
- for (int32_t aOrB = 0; aOrB < 2; aOrB++) {
- for (int32_t sortA = 0; sortA < 2; sortA++) {
- for (int32_t sortB = 0; sortB < 2; sortB++) {
- for (int32_t slice = -4; slice <= 4; slice++) {
- BSONObj sortOrder;
- if (aOrB == 0) {
- sortOrder = BSON("a" << (sortA ? 1 : -1) << "b" << (sortB ? 1 : -1));
- } else {
- sortOrder = BSON("b" << (sortB ? 1 : -1) << "a" << (sortA ? 1 : -1));
- }
-
- setSortMod(slice, eachArray, sortOrder);
- Document doc(getObjectUsing(docArray /* {a: [{a:2,b:b},{a:3,:b1}]} */));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod().prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(mod().apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
-
- vector<BSONObj> combinedVec;
- combineAndSortVec(docArray, /* a: [{a:2,b:3},{a:3,:b1}] */
- eachArray, /* a: [{a:2,b:1}] */
- slice,
- sortOrder,
- &combinedVec);
- ASSERT_EQUALS(getObjectUsing(combinedVec), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(mod().log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(BSON("$set" << getObjectUsing(combinedVec)), logDoc);
- }
- }
- }
- }
-}
-
-// Push to position tests
-
-TEST(ToPosition, BadInputs) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
- const char* const bad[] = {
- "{$push: {a: { $each: [1], $position:'s'}}}",
- "{$push: {a: { $each: [1], $position:{}}}}",
- "{$push: {a: { $each: [1], $position:[0]}}}",
- "{$push: {a: { $each: [1], $position:1.1211212}}}",
- "{$push: {a: { $each: [1], $position:3.000000000001}}}",
- "{$push: {a: { $each: [1], $position:1.2}}}",
- "{$push: {a: { $each: [1], $position:-1.2}}}",
- "{$push: {a: { $each: [1], $position:2147483648}}}",
- "{$push: {a: { $each: [1], $position:-2147483649}}}",
- "{$push: {a: { $each: [1], $position:NaN}}}",
- NULL,
- };
-
- int i = 0;
- while (bad[i] != NULL) {
- ModifierPush pushMod;
- BSONObj modObj = fromjson(bad[i]);
- ASSERT_NOT_OK(pushMod.init(modObj.firstElement().embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- i++;
- }
-}
-
-TEST(ToPosition, GoodInputs) {
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position: NumberLong(1)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:NumberInt(100)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1.0}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1000000}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
- {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
- }
-}
-
-TEST(ToPosition, EmptyArrayFront) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
-}
-
-TEST(ToPosition, EmptyArrayBackBigPosition) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1000}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
-}
-
-TEST(ToPosition, EmptyArrayBack) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1]}}"), logDoc);
-}
-
-TEST(ToPosition, Front) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:0}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a':[1, 0]}}"), logDoc);
-}
-
-TEST(ToPosition, Back) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: { $each: [1], $position:100}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0,1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.1':1}}"), logDoc);
-}
-
-TEST(ToPosition, NegativePositionEmptyArray) {
- Document doc(fromjson("{a: []}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1], $position: -1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a': [1]}}"), logDoc);
-}
-
-TEST(ToPosition, NegativePositionOneElementArray) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1], $position: -1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a': [1, 0]}}"), logDoc);
-}
-
-TEST(ToPosition, NegativePositionMultiElementArray) {
- Document doc(fromjson("{a: [0, 1, 2, 3, 4]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [5], $position: -2}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0, 1, 2, 5, 3, 4]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a': [0, 1, 2, 5, 3, 4]}}"), logDoc);
-}
-
-TEST(ToPosition, NegativePositionOutOfBoundsDefaultsToBeginning) {
- Document doc(fromjson("{a: [0]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [1], $position: -2}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a': [1, 0]}}"), logDoc);
-}
-
-TEST(ToPosition, NegativePositionPushMultipleElements) {
- Document doc(fromjson("{a: [0, 1, 2, 3, 4]}"));
- Mod pushMod(fromjson("{$push: {a: {$each: [5, 6, 7], $position: -2}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(pushMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(pushMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [0, 1, 2, 5, 6, 7, 3, 4]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(pushMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a': [0, 1, 2, 5, 6, 7, 3, 4]}}"), logDoc);
-}
-
-TEST(IndexedMod, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$push: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod mod(fromjson("{$push: {'a.0.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod mod(fromjson("{$push: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(mod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_rename.cpp b/src/mongo/db/ops/modifier_rename.cpp
deleted file mode 100644
index 375edd4d812..00000000000
--- a/src/mongo/db/ops/modifier_rename.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_rename.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/algorithm.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace str = mongoutils::str;
-
-struct ModifierRename::PreparedState {
- PreparedState(mutablebson::Element root)
- : doc(root.getDocument()),
- fromElemFound(doc.end()),
- toIdxFound(0),
- toElemFound(doc.end()),
- applyCalled(false) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // The element to rename
- mutablebson::Element fromElemFound;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t toIdxFound;
-
- // Element to remove (in the destination position)
- mutablebson::Element toElemFound;
-
- // Was apply called?
- bool applyCalled;
-};
-
-ModifierRename::ModifierRename() : _fromFieldRef(), _toFieldRef() {}
-
-ModifierRename::~ModifierRename() {}
-
-Status ModifierRename::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- if (modExpr.type() != String) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The 'to' field for $rename must be a string: " << modExpr);
- }
-
- if (modExpr.valueStringData().find('\0') != std::string::npos) {
- return Status(ErrorCodes::BadValue,
- "The 'to' field for $rename cannot contain an embedded null byte");
- }
-
- // Extract the field names from the mod expression
-
- _fromFieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fromFieldRef);
- if (!status.isOK())
- return status;
-
- _toFieldRef.parse(modExpr.String());
- status = fieldchecker::isUpdatable(_toFieldRef);
- if (!status.isOK())
- return status;
-
- // TODO: Remove this restriction and make a noOp to lift restriction
- // Old restriction is that if the fields are the same then it is not allowed.
- if (_fromFieldRef == _toFieldRef)
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source and target field for $rename must differ: "
- << modExpr);
-
- // TODO: Remove this restriction by allowing moving deeping from the 'from' path
- // Old restriction is that if the to/from is on the same path it fails
- if (_fromFieldRef.isPrefixOf(_toFieldRef) || _toFieldRef.isPrefixOf(_fromFieldRef)) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source and target field for $rename must "
- "not be on the same path: "
- << modExpr);
- }
- // TODO: We can remove this restriction as long as there is only one,
- // or it is the same array -- should think on this a bit.
- //
- // If a $-positional operator was used it is an error
- size_t dummyPos;
- if (fieldchecker::isPositional(_fromFieldRef, &dummyPos))
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source field for $rename may not be dynamic: "
- << _fromFieldRef.dottedField());
- else if (fieldchecker::isPositional(_toFieldRef, &dummyPos))
- return Status(ErrorCodes::BadValue,
- str::stream() << "The destination field for $rename may not be dynamic: "
- << _toFieldRef.dottedField());
-
- if (positional)
- *positional = false;
-
- return Status::OK();
-}
-
-Status ModifierRename::prepare(mutablebson::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
- // Rename doesn't work with positional fields ($)
- dassert(matchedField.empty());
-
- _preparedState.reset(new PreparedState(root));
-
- // Locate the to field name in 'root', which must exist.
- size_t fromIdxFound;
- Status status = pathsupport::findLongestPrefix(
- _fromFieldRef, root, &fromIdxFound, &_preparedState->fromElemFound);
-
- const bool sourceExists =
- (_preparedState->fromElemFound.ok() && fromIdxFound == (_fromFieldRef.numParts() - 1));
-
- // If we can't find the full element in the from field then we can't do anything.
- if (!status.isOK() || !sourceExists) {
- execInfo->noOp = true;
- _preparedState->fromElemFound = root.getDocument().end();
-
- // TODO: remove this special case from existing behavior
- if (status.code() == ErrorCodes::PathNotViable) {
- return status;
- }
-
- return Status::OK();
- }
-
- // Ensure no array in ancestry if what we found is not at the root
- mutablebson::Element curr = _preparedState->fromElemFound.parent();
- if (curr != curr.getDocument().root())
- while (curr.ok() && (curr != curr.getDocument().root())) {
- if (curr.getType() == Array)
- return Status(ErrorCodes::BadValue,
- str::stream() << "The source field cannot be an array element, '"
- << _fromFieldRef.dottedField()
- << "' in doc with "
- << findElementNamed(root.leftChild(), "_id").toString()
- << " has an array field called '"
- << curr.getFieldName()
- << "'");
- curr = curr.parent();
- }
-
- // "To" side validation below
-
- status = pathsupport::findLongestPrefix(
- _toFieldRef, root, &_preparedState->toIdxFound, &_preparedState->toElemFound);
-
- // FindLongestPrefix may return not viable or any other error and then we cannot proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- // Not an error condition as we will create the "to" path as needed.
- } else if (!status.isOK()) {
- return status;
- }
-
- const bool destExists = _preparedState->toElemFound.ok() &&
- (_preparedState->toIdxFound == (_toFieldRef.numParts() - 1));
-
- // Ensure no array in ancestry of "to" Element
- // Set to either parent, or node depending on if the full path element was found
- curr = (destExists ? _preparedState->toElemFound.parent() : _preparedState->toElemFound);
- if (curr != curr.getDocument().root()) {
- while (curr.ok()) {
- if (curr.getType() == Array)
- return Status(ErrorCodes::BadValue,
- str::stream() << "The destination field cannot be an array element, '"
- << _fromFieldRef.dottedField()
- << "' in doc with "
- << findElementNamed(root.leftChild(), "_id").toString()
- << " has an array field called '"
- << curr.getFieldName()
- << "'");
- curr = curr.parent();
- }
- }
-
- // 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] = &_fromFieldRef;
- execInfo->fieldRef[1] = &_toFieldRef;
-
- execInfo->noOp = false;
-
- return Status::OK();
-}
-
-Status ModifierRename::apply() const {
- dassert(_preparedState->fromElemFound.ok());
-
- _preparedState->applyCalled = true;
-
- // Remove from source
- Status removeStatus = _preparedState->fromElemFound.remove();
- if (!removeStatus.isOK()) {
- return removeStatus;
- }
-
- // If there's no need to create any further field part, the op is simply a value
- // assignment.
- const bool destExists = _preparedState->toElemFound.ok() &&
- (_preparedState->toIdxFound == (_toFieldRef.numParts() - 1));
-
- if (destExists) {
- // Set destination element to the value of the source element.
- return _preparedState->toElemFound.setValueElement(_preparedState->fromElemFound);
- }
-
- // Creates the final element that's going to be the in 'doc'.
- mutablebson::Document& doc = _preparedState->doc;
- StringData lastPart = _toFieldRef.getPart(_toFieldRef.numParts() - 1);
- mutablebson::Element elemToSet =
- doc.makeElementWithNewFieldName(lastPart, _preparedState->fromElemFound);
- if (!elemToSet.ok()) {
- return Status(ErrorCodes::InternalError, "can't create new element");
- }
-
- // Find the new place to put the "to" element:
- // createPathAt does not use existing prefix elements so we
- // need to get the prefix match position for createPathAt below
- size_t tempIdx = 0;
- mutablebson::Element tempElem = doc.end();
- Status status = pathsupport::findLongestPrefix(_toFieldRef, doc.root(), &tempIdx, &tempElem);
-
- // createPathAt will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(_toFieldRef,
- tempElem == doc.end() ? 0 : tempIdx + 1,
- tempElem == doc.end() ? doc.root() : tempElem,
- elemToSet)
- .getStatus();
-}
-
-Status ModifierRename::log(LogBuilder* logBuilder) const {
- // If there was no element found then it was a noop, so return immediately
- if (!_preparedState->fromElemFound.ok())
- return Status::OK();
-
- // debug assert if apply not called, since we found an element to move.
- dassert(_preparedState->applyCalled);
-
- const bool isPrefix = _fromFieldRef.isPrefixOf(_toFieldRef);
- const StringData setPath = (isPrefix ? _fromFieldRef : _toFieldRef).dottedField();
- const StringData unsetPath = isPrefix ? StringData() : _fromFieldRef.dottedField();
- const bool doUnset = !isPrefix;
-
- // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'.
- // We start by creating the {$set: ...} Element.
- mutablebson::Document& doc = logBuilder->getDocument();
-
- // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
- // dotted field, if it was applied over a dotted field. The rationale is that the
- // secondary may be in a different state than the primary and thus make different
- // decisions about creating the intermediate path in _fieldRef or not.
- mutablebson::Element logElement =
- doc.makeElementWithNewFieldName(setPath, _preparedState->fromElemFound.getValue());
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $rename mod");
- }
-
- // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} section.
- Status status = logBuilder->addToSets(logElement);
-
- if (status.isOK() && doUnset) {
- // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
- // dotted field, if it was applied over a dotted field. The rationale is that the
- // secondary may be in a different state than the primary and thus make different
- // decisions about creating the intermediate path in _fieldRef or not.
- status = logBuilder->addToUnsets(unsetPath);
- }
-
- return status;
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_rename.h b/src/mongo/db/ops/modifier_rename.h
deleted file mode 100644
index cba8d39fe6e..00000000000
--- a/src/mongo/db/ops/modifier_rename.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-/**
-* The $rename modifier moves the field from source to the destination to perform
-* the rename.
-*
-* Example: {$rename: {<source>:<dest>}} where both <source/dest> are field names
-* Start with {a:1} and applying a {$rename: {"a":"b"} } produces {b:1}
-**/
-class ModifierRename : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierRename);
-
-public:
- ModifierRename();
- virtual ~ModifierRename();
-
- /**
- * We will check that the to/from are valid paths; in prepare more validation is done
- */
- virtual Status init(const BSONElement& modExpr, const Options& opts, bool* positional = NULL);
-
- /**
- * In prepare we will ensure that all restrictions are met:
- * -- The 'from' field exists, and is valid, else it is a no-op
- * -- The 'to' field is valid as a destination
- * -- The 'to' field is not on the path (or the same path) as the 'from' field
- * -- Neither 'to' nor 'from' have an array ancestor
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /**
- * We will transform the document by first making sure that the 'to' element
- * is empty before moving the 'from' element there.
- */
- virtual Status apply() const;
-
- /**
- * For the oplog entry we will generate an $unset on the 'from' field, and $set for
- * the 'to' field. If no 'from' element is found then function will return immediately.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-private:
- // The source and destination fields
- FieldRef _fromFieldRef;
- FieldRef _toFieldRef;
-
- // The state carried over from prepare for apply/log
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_rename_test.cpp b/src/mongo/db/ops/modifier_rename_test.cpp
deleted file mode 100644
index b13337430f2..00000000000
--- a/src/mongo/db/ops/modifier_rename_test.cpp
+++ /dev/null
@@ -1,468 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_rename.h"
-
-#include <cstdint>
-
-#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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-
-using mutablebson::ConstElement;
-using mutablebson::Element;
-
-/** Helper to build and manipulate the mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) {
- _modObj = modObj;
- ASSERT_OK(_mod.init(_modObj["$rename"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierRename& mod() {
- return _mod;
- }
-
-private:
- ModifierRename _mod;
- BSONObj _modObj;
-};
-
-/**
- * These test negative cases:
- * -- No '$' support for positional operator
- * -- No empty field names (ex. .a, b. )
- * -- Can't rename to an invalid fieldname (empty fieldname part)
- */
-TEST(InvalidInit, FromDbTests) {
- ModifierRename mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(fromjson("{'a.$':'b'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- ASSERT_NOT_OK(mod.init(fromjson("{'a':'b.$'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- ASSERT_NOT_OK(mod.init(fromjson("{'.b':'a'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- ASSERT_NOT_OK(mod.init(fromjson("{'b.':'a'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- ASSERT_NOT_OK(mod.init(fromjson("{'b':'.a'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- ASSERT_NOT_OK(mod.init(fromjson("{'b':'a.'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(InvalidInit, ToFieldCannotContainEmbeddedNullByte) {
- ModifierRename mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- {
- const auto embeddedNull = "a\0b"_sd;
- ASSERT_NOT_OK(mod.init(BSON("a" << embeddedNull).firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- }
-
- {
- const auto singleNullByte = "\0"_sd;
- ASSERT_NOT_OK(mod.init(BSON("a" << singleNullByte).firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- }
-
- {
- const auto leadingNullByte = "\0bbbb"_sd;
- ASSERT_NOT_OK(mod.init(BSON("a" << leadingNullByte).firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- }
-
- {
- const auto trailingNullByte = "bbbb\0"_sd;
- ASSERT_NOT_OK(mod.init(BSON("a" << trailingNullByte).firstElement(),
- ModifierInterface::Options::normal(expCtx)));
- }
-}
-
-TEST(MissingFrom, InitPrepLog) {
- mutablebson::Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'b':'a'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(MissingFromDotted, InitPrepLog) {
- mutablebson::Document doc(fromjson("{a: {r:2}}"));
- Mod setMod(fromjson("{$rename: {'a.b':'a.c'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_TRUE(execInfo.noOp);
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(BasicInit, DifferentRoots) {
- mutablebson::Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'f.g'}}"));
-}
-
-TEST(MoveOnSamePath, MoveUp) {
- ModifierRename mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(fromjson("{'b.a':'b'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(MoveOnSamePath, MoveDown) {
- ModifierRename mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(fromjson("{'b':'b.a'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(MoveOnSamePath, MoveToSelf) {
- ModifierRename mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(fromjson("{'b.a':'b.a'}").firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-TEST(MissingTo, SimpleNumberAtRoot) {
- mutablebson::Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:2}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(SimpleReplace, SameLevel) {
- mutablebson::Document doc(fromjson("{a: 2, b: 1}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:2}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(SimpleReplace, FromDottedElement) {
- mutablebson::Document doc(fromjson("{a: {c: {d: 6}}, b: 1}"));
- Mod setMod(fromjson("{$rename: {'a.c':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.c");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a: {}, b:{ d: 6}}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': {d: 6}}, $unset: {'a.c': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(SimpleReplace, RenameToExistingFieldDoesNotReorderFields) {
- mutablebson::Document doc(fromjson("{a: 1, b: 2, c: 3}"));
- Mod setMod(fromjson("{$rename: {a: 'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b: 1, c: 3}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set: {b: 1}, $unset: {a: true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(SimpleReplace, RenameToExistingNestedFieldDoesNotReorderFields) {
- mutablebson::Document doc(fromjson("{a: {b: {c: 1, d: 2}}, b: 3, c: {d: 4}}"));
- Mod setMod(fromjson("{$rename: {'c.d': 'a.b.c'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "c.d");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "a.b.c");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a: {b: {c: 4, d: 2}}, b: 3, c: {}}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set: {'a.b.c': 4}, $unset: {'c.d': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(DottedTo, MissingCompleteTo) {
- mutablebson::Document doc(fromjson("{a: 2, b: 1, c: {}}"));
- Mod setMod(fromjson("{$rename: {'a':'c.r.d'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "c.r.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:1, c: { r: { d: 2}}}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'c.r.d': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(DottedTo, ToIsCompletelyMissing) {
- mutablebson::Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'b.c.d'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b.c.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b: {c: {d: 2}}}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b.c.d': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(FromArrayOfEmbeddedDocs, ToMissingDottedField) {
- mutablebson::Document doc(fromjson("{a: [ {a:2, b:1} ] }"));
- Mod setMod(fromjson("{$rename: {'a':'b.c.d'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b.c.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b: {c: {d: [ {a:2, b:1} ]}}}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b.c.d': [ {a:2, b:1} ]}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(FromArrayOfEmbeddedDocs, ToArray) {
- mutablebson::Document doc(fromjson("{a: [ {a:2, b:1} ] }"));
- Mod setMod(fromjson("{$rename: {'a.a':'a.b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(Arrays, MoveInto) {
- mutablebson::Document doc(fromjson("{a: [1, 2], b:2}"));
- Mod setMod(fromjson("{$rename: {'b':'a.2'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(Arrays, MoveOut) {
- mutablebson::Document doc(fromjson("{a: [1, 2]}"));
- Mod setMod(fromjson("{$rename: {'a.0':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(Arrays, MoveNonexistantEmbeddedFieldOut) {
- mutablebson::Document doc(fromjson("{a: [{a:1}, {b:2}]}"));
- Mod setMod(fromjson("{$rename: {'a.a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(Arrays, MoveEmbeddedFieldOutWithElementNumber) {
- mutablebson::Document doc(fromjson("{a: [{a:1}, {b:2}]}"));
- Mod setMod(fromjson("{$rename: {'a.0.a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(Arrays, ReplaceArrayField) {
- mutablebson::Document doc(fromjson("{a: 2, b: []}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:2}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': 2}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-
-TEST(Arrays, ReplaceWithArrayField) {
- mutablebson::Document doc(fromjson("{a: [], b: 2}"));
- Mod setMod(fromjson("{$rename: {'a':'b'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{b:[]}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'b': []}, $unset: {'a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-TEST(LegacyData, CanRenameFromInvalidFieldName) {
- mutablebson::Document doc(fromjson("{$a: 2}"));
- Mod setMod(fromjson("{$rename: {'$a':'a'}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "$a");
- ASSERT_EQUALS(execInfo.fieldRef[1]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(doc, fromjson("{a:2}"));
-
- mutablebson::Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- BSONObj logObj = fromjson("{$set:{ 'a': 2}, $unset: {'$a': true}}");
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(logDoc, logObj);
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_set.cpp b/src/mongo/db/ops/modifier_set.cpp
deleted file mode 100644
index 5e3b89d1c1a..00000000000
--- a/src/mongo/db/ops/modifier_set.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_set.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace str = mongoutils::str;
-
-struct ModifierSet::PreparedState {
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc), idxFound(0), elemFound(doc.end()), noOp(false), elemIsBlocking(false) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-
- // This $set is a no-op?
- bool noOp;
-
- // The element we find during a replication operation that blocks our update path
- bool elemIsBlocking;
-};
-
-ModifierSet::ModifierSet(ModifierSet::ModifierSetMode mode)
- : _fieldRef(), _posDollar(0), _setMode(mode), _val() {}
-
-ModifierSet::~ModifierSet() {}
-
-Status ModifierSet::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- //
- // field name analysis
- //
-
- // Break down the field name into its 'dotted' components (aka parts) and check that
- // the field is fit for updates
- _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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
- //
- // value analysis
- //
-
- if (!modExpr.ok())
- return Status(ErrorCodes::BadValue, "cannot $set an empty value");
-
- _val = modExpr;
- _fromOplogApplication = opts.fromOplogApplication;
-
- return Status::OK();
-}
-
-Status ModifierSet::prepare(mutablebson::Element root,
- 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // 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 remainin path, if missing, will
- // be created during the apply.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
- const auto elemFoundIsArray =
- _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
-
- // 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 (_fromOplogApplication && status.code() == ErrorCodes::PathNotViable) {
- // If we are applying an oplog entry and it is an invalid path, then push on indicating that
- // we had a blocking element, which we stopped at
- _preparedState->elemIsBlocking = true;
- } else if (!status.isOK()) {
- return status;
- }
-
- if (_setMode == SET_ON_INSERT) {
- execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT;
- }
-
- // 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;
-
- //
- // in-place and no-op logic
- //
-
- // If the field path is not fully present, then this mod cannot be in place, nor is it a noOp.
- if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- if (elemFoundIsArray) {
- // Report that an existing array will gain a new element as a result of this mod.
- execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
- }
- return Status::OK();
- }
-
- // If the value being $set is the same as the one already in the doc, than this is a noOp. We
- // use binary equality to compare so that any change to the document is considered, unlike using
- // a comparison that winds up in woCompare (see SERVER-16801). In the case where elemFound
- // doesn't have a serialized representation, we just declare the operation to not be a
- // no-op. This is potentially a missed optimization, but is unlikely to cause much pain since in
- // the normal update workflow we only admit one modification on any path from a leaf to the
- // document root. In that domain, hasValue will always be true. We may encounter a
- // non-serialized elemFound in the case where our base document is the result of calling
- // populateDocumentWithQueryFields, so this could cause us to do slightly more work than
- // strictly necessary in the case where an update (w upsert:true) becomes an insert.
- if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1) &&
- _preparedState->elemFound.hasValue() &&
- _preparedState->elemFound.getValue().binaryEqualValues(_val)) {
- execInfo->noOp = _preparedState->noOp = true;
- }
-
- return Status::OK();
-}
-
-Status ModifierSet::apply() const {
- dassert(!_preparedState->noOp);
-
- const bool destExists =
- _preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1);
- // If there's no need to create any further field part, the $set is simply a value
- // assignment.
- if (destExists) {
- return _preparedState->elemFound.setValueBSONElement(_val);
- }
-
- //
- // 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.makeElementWithNewFieldName(lastPart, _val);
- 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++;
- }
-
- // Remove the blocking element, if we are from replication applier. See comment below.
- if (_fromOplogApplication && !destExists && _preparedState->elemFound.ok() &&
- _preparedState->elemIsBlocking && (!(_preparedState->elemFound.isType(Array)) ||
- !(_preparedState->elemFound.isType(Object)))) {
- /**
- * With replication we want to be able to remove blocking elements for $set (only).
- * The reason they are blocking elements is that they are not embedded documents
- * (objects) nor an array (a special type of an embedded doc) and we cannot
- * add children to them (because the $set path requires adding children past
- * the blocking element).
- *
- * Imagine that we started with this:
- * {_id:1, a:1} + {$set : {"a.b.c" : 1}} -> {_id:1, a: {b: {c:1}}}
- * Above we found that element (a:1) is blocking at position 1. We now will replace
- * it with an empty object so the normal logic below can be
- * applied from the root (in this example case).
- *
- * Here is an array example:
- * {_id:1, a:[1, 2]} + {$set : {"a.0.c" : 1}} -> {_id:1, a: [ {c:1}, 2]}
- * The blocking element is "a.0" since it is a number, non-object, and we must
- * then replace it with an empty object so we can add c:1 to that empty object
- */
-
- mutablebson::Element blockingElem = _preparedState->elemFound;
- BSONObj newObj;
- // Replace blocking non-object with an empty object
- Status status = blockingElem.setValueObject(newObj);
- if (!status.isOK()) {
- return status;
- }
- }
-
- // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
- return pathsupport::createPathAt(
- _fieldRef, _preparedState->idxFound, _preparedState->elemFound, elemToSet)
- .getStatus();
-}
-
-Status ModifierSet::log(LogBuilder* logBuilder) 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 = logBuilder->getDocument();
-
- // Create the {<fieldname>: <value>} Element. Note that we log the mod with a
- // dotted field, if it was applied over a dotted field. The rationale is that the
- // secondary may be in a different state than the primary and thus make different
- // decisions about creating the intermediate path in _fieldRef or not.
- mutablebson::Element logElement =
- doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _val);
-
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $set mod");
- }
-
- return logBuilder->addToSets(logElement);
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_set.h b/src/mongo/db/ops/modifier_set.h
deleted file mode 100644
index cac403f50a6..00000000000
--- a/src/mongo/db/ops/modifier_set.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-class ModifierSet : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierSet);
-
-public:
- enum ModifierSetMode { SET_NORMAL, SET_ON_INSERT };
- explicit ModifierSet(ModifierSetMode mode = SET_NORMAL);
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierSet();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
- * {$set: {<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, const Options& opts, bool* positional = NULL);
-
- /**
- * Looks up the field name in the sub-tree rooted at 'root', and binds, if necessary,
- * the '$' field part using the 'matchedfield' number. prepare() returns OK and
- * fills in 'execInfo' with information of whether this mod is a no-op on 'root' and
- * whether it is an in-place candidate. Otherwise, returns a status describing the
- * error.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /**
- * Applies the prepared mod over the element 'root' specified in the prepare()
- * call. Returns OK if successful or a status describing the error.
- */
- virtual Status apply() const;
-
- /**
- * Adds a log entry to logRoot corresponding to the operation applied here. Returns OK
- * if successful or a status describing the error.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-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;
-
- // If on 'on insert' mode, We'd like to apply this mod only if we're in a upsert.
- const ModifierSetMode _setMode;
-
- // Element of the $set expression.
- BSONElement _val;
-
- bool _fromOplogApplication = false;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_set_test.cpp b/src/mongo/db/ops/modifier_set_test.cpp
deleted file mode 100644
index e60648e2aaf..00000000000
--- a/src/mongo/db/ops/modifier_set_test.cpp
+++ /dev/null
@@ -1,847 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_set.h"
-
-#include <cstdint>
-
-#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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::BSONObj;
-using mongo::ExpressionContextForTest;
-using mongo::fromjson;
-using mongo::LogBuilder;
-using mongo::ModifierInterface;
-using mongo::NumberInt;
-using mongo::ModifierSet;
-using mongo::Status;
-using mongo::StringData;
-using mongo::mutablebson::ConstElement;
-using mongo::mutablebson::countChildren;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate a $set mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj, bool fromRepl = false)
- : _mod(mongoutils::str::equals(modObj.firstElement().fieldName(), "$setOnInsert")
- ? ModifierSet::SET_ON_INSERT
- : ModifierSet::SET_NORMAL) {
- _modObj = modObj;
- StringData modName = modObj.firstElement().fieldName();
- ASSERT_OK(_mod.init(
- _modObj[modName].embeddedObject().firstElement(),
- !fromRepl ? ModifierInterface::Options::normal(new ExpressionContextForTest())
- : ModifierInterface::Options::fromRepl(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierSet& mod() {
- return _mod;
- }
-
-private:
- ModifierSet _mod;
- BSONObj _modObj;
-};
-
-//
-// Init tests
-//
-
-TEST(Init, EmptyOperation) {
- BSONObj modObj = fromjson("{$set: {}}");
- ModifierSet mod;
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_NOT_OK(mod.init(modObj["$set"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(expCtx)));
-}
-
-//
-// Simple Mods
-//
-
-TEST(SimpleMod, PrepareNoOp) {
- Document doc(fromjson("{a: 2}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareNotNoOp) {
- Document doc(fromjson("{a: NumberInt(2)}"));
- Mod setMod(fromjson("{$set: {a: NumberLong(2)}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareIdentityOpOnDeserializedIsNotANoOp) {
- Document doc(fromjson("{a: { b: NumberInt(0)}}"));
-
- // Apply a mutation to the document that will make it non-serialized.
- doc.root()["a"]["b"].setValueInt(2).transitional_ignore();
-
- // Apply an op that would be a no-op.
- Mod setMod(fromjson("{$set: {a: {b : NumberInt(2)}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareSetOnInsert) {
- Document doc(fromjson("{a: 1}"));
- Mod setMod(fromjson("{$setOnInsert: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
- ASSERT_EQUALS(execInfo.context, ModifierInterface::ExecInfo::INSERT_CONTEXT);
-}
-
-TEST(SimpleMod, PrepareApplyEmptyDocument) {
- Document doc(fromjson("{}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: 2}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyInPlace) {
- Document doc(fromjson("{a: 1}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: 2}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyOverridePath) {
- Document doc(fromjson("{a: {b: 1}}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: 2}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyChangeType) {
- Document doc(fromjson("{a: 'str'}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: 2}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyNewPath) {
- Document doc(fromjson("{b: 1}"));
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc);
-}
-
-TEST(SimpleMod, LogNormal) {
- BSONObj obj = fromjson("{a: 1}");
- Mod setMod(fromjson("{$set: {a: 2}}"));
-
- Document doc(obj);
- ModifierInterface::ExecInfo dummy;
- ASSERT_OK(setMod.prepare(doc.root(), "", &dummy));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {a: 2}}"), logDoc);
-}
-
-//
-// Simple dotted mod
-//
-
-TEST(DottedMod, PrepareNoOp) {
- Document doc(fromjson("{a: {b: 2}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(DottedMod, PrepareNotNoOp) {
- Document doc(fromjson("{a: {b: NumberLong(2)}}"));
- Mod setMod(fromjson("{$set: {'a.b': NumberInt(2)}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(DottedMod, PreparePathNotViable) {
- Document doc(fromjson("{a:1}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(DottedMod, PreparePathNotViableArrray) {
- Document doc(fromjson("{a:[{b:1}]}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(DottedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{a: {b: 1}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyChangeType) {
- Document doc(fromjson("{a: {b: 'str'}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyChangePath) {
- Document doc(fromjson("{a: {b: {c: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyExtendPath) {
- Document doc(fromjson("{a: {c: 1}}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyNewPath) {
- Document doc(fromjson("{c: 1}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyEmptyDoc) {
- Document doc(fromjson("{}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyFieldWithDot) {
- Document doc(fromjson("{'a.b':4}"));
- Mod setMod(fromjson("{$set: {'a.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc);
-}
-
-//
-// Indexed mod
-//
-
-TEST(IndexedMod, PrepareNoOp) {
- Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareNotNoOp) {
- Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2.0}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': NumberInt(2)}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareNonViablePath) {
- Document doc(fromjson("{a: 0}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(IndexedMod, PrepareReportCreatedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod setMod(fromjson("{$set: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_TRUE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_EQUALS(*execInfo.indexOfArrayWithNewElement[0], 0u);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportModifiedArrayElement) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod setMod(fromjson("{$set: {'a.0.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareDoNotReportCreatedNumericObjectField) {
- Document doc(fromjson("{a: {'0': {b: 0}}}"));
- Mod setMod(fromjson("{$set: {'a.1.c': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.c");
- ASSERT_FALSE(execInfo.indexOfArrayWithNewElement[0]);
- ASSERT_FALSE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyNormalArray) {
- Document doc(fromjson("{a: [{b: 0},{b: 1}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyPaddingArray) {
- Document doc(fromjson("{a: [{b: 0}]}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyNumericObject) {
- Document doc(fromjson("{a: {b: 0}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyNumericField) {
- Document doc(fromjson("{a: {'2': {b: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyExtendNumericField) {
- Document doc(fromjson("{a: {'2': {c: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyEmptyObject) {
- Document doc(fromjson("{a: {}}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyEmptyArray) {
- Document doc(fromjson("{a: []}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyInexistent) {
- Document doc(fromjson("{}"));
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.2.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc);
-}
-
-TEST(IndexedMod, LogNormal) {
- BSONObj obj = fromjson("{a: [{b:0}, {b:1}]}");
- Document doc(obj);
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
-}
-
-TEST(IndexedMod, LogEmptyArray) {
- BSONObj obj = fromjson("{a: []}");
- Document doc(obj);
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
-}
-
-TEST(IndexedMod, LogEmptyObject) {
- BSONObj obj = fromjson("{a: []}");
- Document doc(obj);
- Mod setMod(fromjson("{$set: {'a.2.b': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'a.2.b': 2}}"), logDoc);
-}
-
-//
-// Indexed complex mod
-//
-
-TEST(IndexedComplexMod, PrepareNoOp) {
- Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"));
- Mod setMod(fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(IndexedComplexMod, PrepareSameStructure) {
- Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, xxx: 1}}]}}"));
- Mod setMod(fromjson("{$set: {'a.1.b': {c: 1, d: 1}}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_FALSE(execInfo.noOp);
-}
-
-//
-// Replication version where viable paths don't block modification
-//
-TEST(NonViablePathWithoutRepl, ControlRun) {
- Document doc(fromjson("{a: 1}"));
- Mod setMod(fromjson("{$set: {'a.1.b': 1}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(setMod.prepare(doc.root(), "", &execInfo));
-}
-
-TEST(NonViablePathWithRepl, SingleField) {
- Document doc(fromjson("{_id:1, a: 1}"));
- Mod setMod(fromjson("{$set: {'a.1.b': 1}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id:1, a: {'1': {b: 1}}}"), doc);
-}
-
-TEST(NonViablePathWithRepl, SingleFieldNoId) {
- Document doc(fromjson("{a: 1}"));
- Mod setMod(fromjson("{$set: {'a.1.b': 1}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {'1': {b: 1}}}"), doc);
-}
-
-TEST(NonViablePathWithRepl, NestedField) {
- Document doc(fromjson("{_id:1, a: {a: 1}}"));
- Mod setMod(fromjson("{$set: {'a.a.1.b': 1}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.a.1.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id:1, a: {a: {'1': {b: 1}}}}"), doc);
-}
-
-TEST(NonViablePathWithRepl, DoubleNestedField) {
- Document doc(fromjson("{_id:1, a: {b: {c: 1}}}"));
- Mod setMod(fromjson("{$set: {'a.b.c.d': 2}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b.c.d");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id:1, a: {b: {c: {d: 2}}}}"), doc);
-}
-
-TEST(NonViablePathWithRepl, NestedFieldNoId) {
- Document doc(fromjson("{a: {a: 1}}"));
- Mod setMod(fromjson("{$set: {'a.a.1.b': 1}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.a.1.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: {a: {'1': {b: 1}}}}"), doc);
-}
-
-TEST(NonViablePathWithRepl, ReplayArrayFieldNotAppendedItermediate) {
- Document doc(fromjson("{_id: 0, a: [1, {b: [1]}]}"));
- Mod setMod(fromjson("{$set: {'a.0.b': [0,2]}}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id: 0, a: [{b: [0,2]}, {b: [1]}]}"), doc);
-}
-
-// Cases from users/issues/jstests
-TEST(JsTestIssues, Set6) {
- Document doc(fromjson("{_id: 1, r: {a:1, b:2}}"));
- Mod setMod(fromjson("{$set: {'r.a': 2}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "r.a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2}}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc);
-}
-
-// Test which failed before and is here to verify correct execution.
-TEST(JsTestIssues, Set6FromRepl) {
- Document doc(fromjson("{_id: 1, r: {a:1, b:2}}"));
- Mod setMod(fromjson("{$set: { 'r.a': 2}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "r.a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_TRUE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2} }"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(setMod.log(&logBuilder));
- ASSERT_EQUALS(countChildren(logDoc.root()), 1u);
- ASSERT_EQUALS(fromjson("{$set: {'r.a': 2}}"), logDoc);
-}
-
-TEST(Ephemeral, ApplySetModToEphemeralDocument) {
- // The following mod when applied to a document constructed node by node exposed a
- // latent debug only defect in mutable BSON, so this is more a test of mutable than
- // $set.
- Document doc;
- Element x = doc.makeElementObject("x");
- doc.root().pushBack(x).transitional_ignore();
- Element a = doc.makeElementInt("a", 100);
- x.pushBack(a).transitional_ignore();
-
- Mod setMod(fromjson("{ $set: { x: { a: 100, b: 2 }}}"), true);
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(setMod.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "x");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(setMod.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc);
-}
-
-} // unnamed namespace
diff --git a/src/mongo/db/ops/modifier_unset.cpp b/src/mongo/db/ops/modifier_unset.cpp
deleted file mode 100644
index a70db3c8fd4..00000000000
--- a/src/mongo/db/ops/modifier_unset.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_unset.h"
-
-#include "mongo/base/error_codes.h"
-#include "mongo/bson/mutable/document.h"
-#include "mongo/db/update/field_checker.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/db/update/path_support.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
-namespace str = mongoutils::str;
-
-struct ModifierUnset::PreparedState {
- PreparedState(mutablebson::Document* targetDoc)
- : doc(*targetDoc), idxFound(0), elemFound(doc.end()), noOp(false) {}
-
- // Document that is going to be changed.
- mutablebson::Document& doc;
-
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
-
- // Element corresponding to _fieldRef[0.._idxFound].
- mutablebson::Element elemFound;
-
- // This $set is a no-op?
- bool noOp;
-};
-
-ModifierUnset::ModifierUnset() : _fieldRef(), _posDollar(0), _val() {}
-
-ModifierUnset::~ModifierUnset() {}
-
-Status ModifierUnset::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
- //
- // 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
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
-
- if (positional)
- *positional = foundDollar;
-
- if (foundDollar && foundCount > 1) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField()
- << "'");
- }
-
-
- //
- // value analysis
- //
-
- // Unset takes any value, since there is no semantics attached to such value.
- _val = modExpr;
-
- return Status::OK();
-}
-
-Status ModifierUnset::prepare(mutablebson::Element root,
- 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,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
-
- // Locate the field name in 'root'. Note that if we don't have the full path in the
- // doc, there isn't anything to unset, really.
- Status status = pathsupport::findLongestPrefix(
- _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
- if (!status.isOK() || _preparedState->idxFound != (_fieldRef.numParts() - 1)) {
- execInfo->noOp = _preparedState->noOp = true;
- execInfo->fieldRef[0] = &_fieldRef;
-
- return Status::OK();
- }
-
- // If there is indeed something to unset, we register so, along with the interest in
- // the field name. The driver needs this info to sort out if there is any conflict
- // among mods.
- execInfo->fieldRef[0] = &_fieldRef;
-
- // The only way for an $unset to be inplace is for its target field to be the last one
- // of the object. That is, it is always the right child on its paths. The current
- // rationale is that there should be no holes in a BSONObj and, to be in place, no
- // field boundaries must change.
- //
- // TODO:
- // mutablebson::Element curr = _preparedState->elemFound;
- // while (curr.ok()) {
- // if (curr.rightSibling().ok()) {
- // }
- // curr = curr.parent();
- // }
-
- return Status::OK();
-}
-
-Status ModifierUnset::apply() const {
- dassert(!_preparedState->noOp);
-
- // Our semantics says that, if we're unseting an element of an array, we swap that
- // value to null. The rationale is that we don't want other array elements to change
- // indices. (That could be achieved with $pull-ing element from it.)
- if (_preparedState->elemFound.parent().ok() &&
- _preparedState->elemFound.parent().getType() == Array) {
- return _preparedState->elemFound.setValueNull();
- } else {
- return _preparedState->elemFound.remove();
- }
-}
-
-Status ModifierUnset::log(LogBuilder* logBuilder) const {
- return logBuilder->addToUnsets(_fieldRef.dottedField());
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_unset.h b/src/mongo/db/ops/modifier_unset.h
deleted file mode 100644
index a343047c4a7..00000000000
--- a/src/mongo/db/ops/modifier_unset.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * 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/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/bson/mutable/element.h"
-#include "mongo/db/field_ref.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/ops/modifier_interface.h"
-
-namespace mongo {
-
-class LogBuilder;
-
-class ModifierUnset : public ModifierInterface {
- MONGO_DISALLOW_COPYING(ModifierUnset);
-
-public:
- ModifierUnset();
-
- //
- // Modifier interface implementation
- //
-
- virtual ~ModifierUnset();
-
- /**
- * A 'modExpr' is a BSONElement {<fieldname>: <value>} coming from a $set mod such as
- * {$unset: {<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, const Options& opts, bool* positional = NULL);
-
- /**
- * Locates the field to be removed under the 'root' element, if it exist, and fills in
- * 'execInfo' accordingly. Return OK if successful or a status describing the error.
- */
- virtual Status prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo);
-
- /**
- * Removes the found element from the document. If such element was inside an array,
- * removal means setting that array position to 'null'.
- */
- virtual Status apply() const;
-
- /**
- * Adds the exact $unset mod to the log.
- */
- virtual Status log(LogBuilder* logBuilder) const;
-
- virtual void setCollator(const CollatorInterface* collator){};
-
-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.
- BSONElement _val;
-
- // The instance of the field in the provided doc. This state is valid after a
- // prepare() was issued and until a log() is issued. The document this mod is
- // being prepared against must be live throughout all the calls.
- struct PreparedState;
- std::unique_ptr<PreparedState> _preparedState;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/ops/modifier_unset_test.cpp b/src/mongo/db/ops/modifier_unset_test.cpp
deleted file mode 100644
index a4f159e3347..00000000000
--- a/src/mongo/db/ops/modifier_unset_test.cpp
+++ /dev/null
@@ -1,456 +0,0 @@
-/**
- * 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/>.
- *
- * 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/db/ops/modifier_unset.h"
-
-#include <cstdint>
-
-#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/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/update/log_builder.h"
-#include "mongo/unittest/unittest.h"
-
-namespace {
-
-using mongo::Array;
-using mongo::BSONObj;
-using mongo::ExpressionContextForTest;
-using mongo::fromjson;
-using mongo::LogBuilder;
-using mongo::ModifierInterface;
-using mongo::ModifierUnset;
-using mongo::Status;
-using mongo::StringData;
-using mongo::mutablebson::Document;
-using mongo::mutablebson::Element;
-
-/** Helper to build and manipulate a $set mod. */
-class Mod {
-public:
- Mod() : _mod() {}
-
- explicit Mod(BSONObj modObj) {
- _modObj = modObj;
- ASSERT_OK(_mod.init(_modObj["$unset"].embeddedObject().firstElement(),
- ModifierInterface::Options::normal(new ExpressionContextForTest())));
- }
-
- Status prepare(Element root, StringData matchedField, ModifierInterface::ExecInfo* execInfo) {
- return _mod.prepare(root, matchedField, execInfo);
- }
-
- Status apply() const {
- return _mod.apply();
- }
-
- Status log(LogBuilder* logBuilder) const {
- return _mod.log(logBuilder);
- }
-
- ModifierUnset& mod() {
- return _mod;
- }
-
- BSONObj modObj() {
- return _modObj;
- }
-
-private:
- ModifierUnset _mod;
- BSONObj _modObj;
-};
-
-//
-// Simple mod
-//
-
-TEST(SimpleMod, PrepareNoOp) {
- Document doc(fromjson("{}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(SimpleMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: 1, b: 2}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{b: 2}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyInPlace) {
- Document doc(fromjson("{x: 0, a: 1}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{x: 0}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyGeneratesEmptyDocument) {
- Document doc(fromjson("{a: 1}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{}"), doc);
-}
-
-TEST(SimpleMod, PrepareApplyUnsetSubtree) {
- Document doc(fromjson("{a: {b: 1}, c: 2}"));
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{c: 2}"), doc);
-}
-
-TEST(SimpleMod, LogNormal) {
- BSONObj obj = fromjson("{a: 1}");
- Document doc(obj);
- Mod modUnset(fromjson("{$unset: {a: true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a");
- ASSERT_FALSE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(modUnset.modObj(), logDoc);
-}
-
-//
-// Dotted mod
-//
-
-TEST(DottedMod, PrepareNoOp) {
- Document doc(fromjson("{c:2}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(DottedMod, PrepareApplyNormal) {
- Document doc(fromjson("{a: {b: 1}, c: 2}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:{}, c:2}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{x: 0, a: {b: 1}}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{x: 0, a:{}}"), doc);
-}
-
-TEST(DottedMod, PrepareApplyUnsetNestedSubobject) {
- Document doc(fromjson("{a: {b: {c: 1}}}"));
- Mod modUnset(fromjson("{$unset: {'a.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{a: {}}"), doc);
-}
-
-//
-// Indexed mod
-//
-
-TEST(IndexedMod, PrepareNoOp) {
- Document doc(fromjson("{a:[]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(IndexedMod, PrepareApplyNormal) {
- Document doc(fromjson("{a:[0,1,2]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[null,1,2]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyInPlace) {
- Document doc(fromjson("{b:1, a:[1]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{b:1, a:[null]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyInPlaceNuance) {
- // Can't change the encoding in the middle of a bson stream.
- Document doc(fromjson("{a:[1], b:1}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyInnerObject) {
- Document doc(fromjson("{a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.0.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[{}]}"), doc);
-}
-
-TEST(IndexedMod, PrepareApplyObject) {
- Document doc(fromjson("{a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a:[null]}"), doc);
-}
-
-TEST(IndexedMod, LogNormal) {
- Document doc(fromjson("{a:[0,1,2]}"));
- Mod modUnset(fromjson("{$unset: {'a.0': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "", &execInfo));
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(modUnset.modObj(), logDoc);
-}
-
-//
-// Positional mod
-//
-
-TEST(PositionalMod, PrepareNoOp) {
- Document doc(fromjson("{a:[{b:0}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "1", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.1.b");
- ASSERT_TRUE(execInfo.noOp);
-}
-
-TEST(PositionalMod, PrepareMissingPositional) {
- Document doc(fromjson("{a:[{b:0},{c:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_NOT_OK(modUnset.prepare(doc.root(), "" /* no position */, &execInfo));
-}
-
-TEST(PositionalMod, PrepareApplyNormal) {
- Document doc(fromjson("{a:[{b:0},{c:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [{}, {c:1}]}"), doc);
-}
-
-TEST(PositionalMod, PrepareApplyObject) {
- Document doc(fromjson("{a:[{b:0},{c:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{a: [null, {c:1}]}"), doc);
-}
-
-TEST(PositionalMod, PrepareApplyInPlace) {
- Document doc(fromjson("{b:1, a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled()); // TODO turn in-place on for this.
- ASSERT_EQUALS(fromjson("{b:1, a:[{}]}"), doc);
-}
-
-TEST(PositionalMod, LogNormal) {
- Document doc(fromjson("{b:1, a:[{b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.b");
- ASSERT_FALSE(execInfo.noOp);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$unset: {'a.0.b': true}}"), logDoc);
-}
-
-TEST(LegacyData, CanUnsetInvalidField) {
- Document doc(fromjson("{b:1, a:[{$b:1}]}"));
- Mod modUnset(fromjson("{$unset: {'a.$.$b': true}}"));
-
- ModifierInterface::ExecInfo execInfo;
- ASSERT_OK(modUnset.prepare(doc.root(), "0", &execInfo));
-
- ASSERT_EQUALS(execInfo.fieldRef[0]->dottedField(), "a.0.$b");
- ASSERT_FALSE(execInfo.noOp);
-
- ASSERT_OK(modUnset.apply());
- ASSERT_FALSE(doc.isInPlaceModeEnabled());
- ASSERT_EQUALS(fromjson("{b:1, a:[{}]}"), doc);
-
- Document logDoc;
- LogBuilder logBuilder(logDoc.root());
- ASSERT_OK(modUnset.log(&logBuilder));
- ASSERT_EQUALS(fromjson("{$unset: {'a.0.$b': true}}"), logDoc);
-}
-
-
-} // unnamed namespace
diff --git a/src/mongo/db/ops/parsed_update.cpp b/src/mongo/db/ops/parsed_update.cpp
index 7dd6444bc6b..1b7d6fbec6f 100644
--- a/src/mongo/db/ops/parsed_update.cpp
+++ b/src/mongo/db/ops/parsed_update.cpp
@@ -43,7 +43,7 @@ namespace mongo {
ParsedUpdate::ParsedUpdate(OperationContext* opCtx, const UpdateRequest* request)
: _opCtx(opCtx),
_request(request),
- _driver(UpdateDriver::Options(new ExpressionContext(opCtx, nullptr))),
+ _driver(new ExpressionContext(opCtx, nullptr)),
_canonicalQuery() {}
Status ParsedUpdate::parseRequest() {
@@ -141,19 +141,9 @@ Status ParsedUpdate::parseQueryToCQ() {
}
Status ParsedUpdate::parseUpdate() {
- const NamespaceString& ns(_request->getNamespaceString());
-
- // Should the modifiers validate their embedded docs via okForStorage
- // Only user updates should be checked. Any system or replication stuff should pass through.
- // Config db docs shouldn't get checked for valid field names since the shard key can have
- // a dot (".") in it.
- const bool shouldValidate =
- !(_request->isFromOplogApplication() || ns.isConfigDB() || _request->isFromMigration());
-
+ _driver.setCollator(_collator.get());
_driver.setLogOp(true);
- boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(_opCtx, _collator.get()));
- _driver.setModOptions(ModifierInterface::Options(
- _request->isFromOplogApplication(), shouldValidate, std::move(expCtx)));
+ _driver.setFromOplogApplication(_request->isFromOplogApplication());
return _driver.parse(_request->getUpdates(), _arrayFilters, _request->isMulti());
}
diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp
index 8c5b31ab53a..2b1b7225513 100644
--- a/src/mongo/db/ops/update.cpp
+++ b/src/mongo/db/ops/update.cpp
@@ -113,8 +113,7 @@ BSONObj applyUpdateOperators(OperationContext* opCtx,
const BSONObj& operators) {
const CollatorInterface* collator = nullptr;
boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator));
- UpdateDriver::Options opts(std::move(expCtx));
- UpdateDriver driver(opts);
+ UpdateDriver driver(std::move(expCtx));
std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
Status status = driver.parse(operators, arrayFilters);
if (!status.isOK()) {
@@ -123,13 +122,9 @@ BSONObj applyUpdateOperators(OperationContext* opCtx,
mutablebson::Document doc(from, mutablebson::Document::kInPlaceDisabled);
- // The original document can be empty because it is only needed for validation of immutable
- // paths.
- const BSONObj emptyOriginal;
const bool validateForStorage = false;
const FieldRefSet emptyImmutablePaths;
- status =
- driver.update(StringData(), emptyOriginal, &doc, validateForStorage, emptyImmutablePaths);
+ status = driver.update(StringData(), &doc, validateForStorage, emptyImmutablePaths);
if (!status.isOK()) {
uasserted(16839, status.reason());
}