diff options
Diffstat (limited to 'src/mongo')
55 files changed, 122 insertions, 13060 deletions
diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.cpp b/src/mongo/db/auth/authz_manager_external_state_mock.cpp index fbf8a3b2251..8c89b47cc3f 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_mock.cpp @@ -179,8 +179,7 @@ Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx, namespace mmb = mutablebson; const CollatorInterface* collator = nullptr; boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator)); - UpdateDriver::Options updateOptions(std::move(expCtx)); - UpdateDriver driver(updateOptions); + UpdateDriver driver(std::move(expCtx)); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; Status status = driver.parse(updatePattern, arrayFilters); if (!status.isOK()) @@ -191,16 +190,11 @@ Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx, mmb::Document document; if (status.isOK()) { document.reset(*iter, mmb::Document::kInPlaceDisabled); - const BSONObj emptyOriginal; const bool validateForStorage = false; const FieldRefSet emptyImmutablePaths; BSONObj logObj; - status = driver.update(StringData(), - emptyOriginal, - &document, - validateForStorage, - emptyImmutablePaths, - &logObj); + status = driver.update( + StringData(), &document, validateForStorage, emptyImmutablePaths, &logObj); if (!status.isOK()) return status; BSONObj newObj = document.getObject().copy(); @@ -224,13 +218,9 @@ Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx, return status; } - // 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, &document, validateForStorage, emptyImmutablePaths); + status = driver.update(StringData(), &document, validateForStorage, emptyImmutablePaths); if (!status.isOK()) { return status; } diff --git a/src/mongo/db/auth/role_graph_update.cpp b/src/mongo/db/auth/role_graph_update.cpp index 96988cd5d80..dc90439f134 100644 --- a/src/mongo/db/auth/role_graph_update.cpp +++ b/src/mongo/db/auth/role_graph_update.cpp @@ -203,9 +203,8 @@ Status handleOplogUpdate(OperationContext* opCtx, return status; boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, nullptr)); - UpdateDriver::Options updateOptions(std::move(expCtx)); - updateOptions.modOptions.fromOplogApplication = true; - UpdateDriver driver(updateOptions); + UpdateDriver driver(std::move(expCtx)); + driver.setFromOplogApplication(true); // Oplog updates do not have array filters. std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; @@ -226,13 +225,9 @@ Status handleOplogUpdate(OperationContext* opCtx, if (!status.isOK()) return status; - // 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, &roleDocument, validateForStorage, emptyImmutablePaths); + status = driver.update(StringData(), &roleDocument, validateForStorage, emptyImmutablePaths); if (!status.isOK()) return status; diff --git a/src/mongo/db/exec/update.cpp b/src/mongo/db/exec/update.cpp index f3a12e32159..53ba1fc9617 100644 --- a/src/mongo/db/exec/update.cpp +++ b/src/mongo/db/exec/update.cpp @@ -176,6 +176,14 @@ UpdateStage::UpdateStage(OperationContext* opCtx, _doc(params.driver->getDocument()) { _children.emplace_back(child); + // Should the modifiers validate their embedded docs via storage_validation::storageValid()? + // Only user updates should be checked. Any system or replication stuff should pass through. + // Config db docs also do not get checked. + const auto request = _params.request; + _enforceOkForStorage = + !(request->isFromOplogApplication() || request->getNamespaceString().isConfigDB() || + request->isFromMigration()); + // Before we even start executing, we know whether or not this is a replacement // style or $mod style update. _specificStats.isDocReplacement = params.driver->isDocReplacement(); @@ -206,8 +214,7 @@ BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj, Reco bool docWasModified = false; Status status = Status::OK(); - const bool validateForStorage = getOpCtx()->writesAreReplicated() && - !request->isFromMigration() && driver->modOptions().enforceOkForStorage; + const bool validateForStorage = getOpCtx()->writesAreReplicated() && _enforceOkForStorage; FieldRefSet immutablePaths; if (getOpCtx()->writesAreReplicated() && !request->isFromMigration()) { if (lifecycle) { @@ -222,13 +229,8 @@ BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj, Reco } if (!driver->needMatchDetails()) { // If we don't need match details, avoid doing the rematch - status = driver->update(StringData(), - oldObj.value(), - &_doc, - validateForStorage, - immutablePaths, - &logObj, - &docWasModified); + status = driver->update( + StringData(), &_doc, validateForStorage, immutablePaths, &logObj, &docWasModified); } else { // If there was a matched field, obtain it. MatchDetails matchDetails; @@ -241,13 +243,8 @@ BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj, Reco if (matchDetails.hasElemMatchKey()) matchedField = matchDetails.elemMatchKey(); - status = driver->update(matchedField, - oldObj.value(), - &_doc, - validateForStorage, - immutablePaths, - &logObj, - &docWasModified); + status = driver->update( + matchedField, &_doc, validateForStorage, immutablePaths, &logObj, &docWasModified); } if (!status.isOK()) { @@ -375,6 +372,7 @@ BSONObj UpdateStage::applyUpdateOpsForInsert(OperationContext* opCtx, mutablebson::Document* doc, bool isInternalRequest, const NamespaceString& ns, + bool enforceOkForStorage, UpdateStats* stats) { // Since this is an insert (no docs found and upsert:true), we will be logging it // as an insert in the oplog. We don't need the driver's help to build the @@ -393,18 +391,13 @@ BSONObj UpdateStage::applyUpdateOpsForInsert(OperationContext* opCtx, } immutablePaths.keepShortest(&idFieldRef); - // The original document we compare changes to - immutable paths must not change - BSONObj original; - if (cq) { uassertStatusOK(driver->populateDocumentWithQueryFields(*cq, immutablePaths, *doc)); if (driver->isDocReplacement()) stats->fastmodinsert = true; - original = doc->getObject(); } else { fassert(17354, CanonicalQuery::isSimpleIdQuery(query)); BSONElement idElt = query[idFieldName]; - original = idElt.wrap(); fassert(17352, doc->root().appendElement(idElt)); } @@ -414,8 +407,7 @@ BSONObj UpdateStage::applyUpdateOpsForInsert(OperationContext* opCtx, if (isInternalRequest) { immutablePaths.clear(); } - Status updateStatus = - driver->update(StringData(), original, doc, validateForStorage, immutablePaths); + Status updateStatus = driver->update(StringData(), doc, validateForStorage, immutablePaths); if (!updateStatus.isOK()) { uasserted(16836, updateStatus.reason()); } @@ -432,7 +424,7 @@ BSONObj UpdateStage::applyUpdateOpsForInsert(OperationContext* opCtx, // that contains all the immutable keys and can be stored if it isn't coming // from a migration or via replication. if (!isInternalRequest) { - if (driver->modOptions().enforceOkForStorage) { + if (enforceOkForStorage) { storage_validation::storageValid(*doc); } checkImmutablePathsPresent(*doc, immutablePaths); @@ -463,6 +455,7 @@ void UpdateStage::doInsert() { &_doc, isInternalRequest, request->getNamespaceString(), + _enforceOkForStorage, &_specificStats); _specificStats.objInserted = newObj; diff --git a/src/mongo/db/exec/update.h b/src/mongo/db/exec/update.h index f5dccd1c7e6..0755a6fbcff 100644 --- a/src/mongo/db/exec/update.h +++ b/src/mongo/db/exec/update.h @@ -141,6 +141,7 @@ public: mutablebson::Document* doc, bool isInternalRequest, const NamespaceString& ns, + bool enforceOkForStorage, UpdateStats* stats); private: @@ -192,6 +193,9 @@ private: // Stats UpdateStats _specificStats; + // True if updated documents should be validated with storage_validation::storageValid(). + bool _enforceOkForStorage; + // If the update was in-place, we may see it again. This only matters if we're doing // a multi-update; if we're not doing a multi-update we stop after one update and we // won't see any more docs. 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()); } diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index 082ae597b66..37895498200 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -90,7 +90,7 @@ env.Library( 'update_object_node.cpp', ], LIBDEPS=[ - '$BUILD_DIR/mongo/db/ops/update', + '$BUILD_DIR/mongo/db/logical_clock', '$BUILD_DIR/mongo/db/update_index_data', 'update_common', ], @@ -141,7 +141,6 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/common', - '$BUILD_DIR/mongo/db/ops/update', '$BUILD_DIR/mongo/db/server_options_core', '$BUILD_DIR/mongo/db/query/query_planner', 'update', diff --git a/src/mongo/db/update/log_builder.h b/src/mongo/db/update/log_builder.h index 9d7b1954365..60287ecfa92 100644 --- a/src/mongo/db/update/log_builder.h +++ b/src/mongo/db/update/log_builder.h @@ -34,17 +34,12 @@ namespace mongo { /** - * There are two update subsystems in MongoDB with slightly different semantics. + * Previously, there were multiple supported versions of the update language. */ enum class UpdateSemantics { - // The update system that was in use up until v3.4, which is implemented in ModifierInterface - // and its subclasses. When a single update adds multiple fields, those fields are added in the - // same order as they are specified in the update document. - kModifierInterface = 0, - - // The update system introduced in v3.6, which is implemented in UpdateNode and its subclassees. - // When a single update adds multiple fields, those fields are added in lexicographic order by - // field name. This system introduces support for arrayFilters and $[] syntax. + // The update system introduced in v3.6, and is the only supported system. When a single update + // adds multiple fields, those fields are added in lexicographic order by field name. This + // system introduces support for arrayFilters and $[] syntax. kUpdateNode = 1, // Must be last. diff --git a/src/mongo/db/update/modifier_table.cpp b/src/mongo/db/update/modifier_table.cpp index da5a8e9d8e0..95dfe4146f1 100644 --- a/src/mongo/db/update/modifier_table.cpp +++ b/src/mongo/db/update/modifier_table.cpp @@ -34,18 +34,6 @@ #include "mongo/base/init.h" #include "mongo/base/simple_string_data_comparator.h" #include "mongo/base/status.h" -#include "mongo/db/ops/modifier_add_to_set.h" -#include "mongo/db/ops/modifier_bit.h" -#include "mongo/db/ops/modifier_compare.h" -#include "mongo/db/ops/modifier_current_date.h" -#include "mongo/db/ops/modifier_inc.h" -#include "mongo/db/ops/modifier_pop.h" -#include "mongo/db/ops/modifier_pull.h" -#include "mongo/db/ops/modifier_pull_all.h" -#include "mongo/db/ops/modifier_push.h" -#include "mongo/db/ops/modifier_rename.h" -#include "mongo/db/ops/modifier_set.h" -#include "mongo/db/ops/modifier_unset.h" #include "mongo/db/update/addtoset_node.h" #include "mongo/db/update/arithmetic_node.h" #include "mongo/db/update/bit_node.h" @@ -147,43 +135,6 @@ ModifierType getType(StringData typeStr) { return it->second->type; } -ModifierInterface* makeUpdateMod(ModifierType modType) { - switch (modType) { - case MOD_ADD_TO_SET: - return new ModifierAddToSet; - case MOD_BIT: - return new ModifierBit; - case MOD_CURRENTDATE: - return new ModifierCurrentDate; - case MOD_INC: - return new ModifierInc(ModifierInc::MODE_INC); - case MOD_MAX: - return new ModifierCompare(ModifierCompare::MAX); - case MOD_MIN: - return new ModifierCompare(ModifierCompare::MIN); - case MOD_MUL: - return new ModifierInc(ModifierInc::MODE_MUL); - case MOD_POP: - return new ModifierPop; - case MOD_PULL: - return new ModifierPull; - case MOD_PULL_ALL: - return new ModifierPullAll; - case MOD_PUSH: - return new ModifierPush; - case MOD_SET: - return new ModifierSet(ModifierSet::SET_NORMAL); - case MOD_SET_ON_INSERT: - return new ModifierSet(ModifierSet::SET_ON_INSERT); - case MOD_RENAME: - return new ModifierRename; - case MOD_UNSET: - return new ModifierUnset; - default: - return NULL; - } -} - std::unique_ptr<UpdateLeafNode> makeUpdateLeafNode(ModifierType modType) { switch (modType) { case MOD_ADD_TO_SET: diff --git a/src/mongo/db/update/modifier_table.h b/src/mongo/db/update/modifier_table.h index 048c58b4fb8..86b43620586 100644 --- a/src/mongo/db/update/modifier_table.h +++ b/src/mongo/db/update/modifier_table.h @@ -28,7 +28,6 @@ #pragma once -#include "mongo/db/ops/modifier_interface.h" #include "mongo/db/update/update_leaf_node.h" namespace mongo { @@ -62,12 +61,6 @@ enum ModifierType { ModifierType getType(StringData typeStr); /** - * Instantiate an update mod that corresponds to 'modType' or NULL if 'modType' is not - * valid. The ownership of the new object is the caller's. - */ -ModifierInterface* makeUpdateMod(ModifierType modType); - -/** * Instantiate an UpdateLeafNode that corresponds to 'modType' or nullptr if 'modType' is not valid. */ std::unique_ptr<UpdateLeafNode> makeUpdateLeafNode(ModifierType modType); diff --git a/src/mongo/db/update/modifier_table_test.cpp b/src/mongo/db/update/modifier_table_test.cpp index ca3f82cc1c5..c2e168134dd 100644 --- a/src/mongo/db/update/modifier_table_test.cpp +++ b/src/mongo/db/update/modifier_table_test.cpp @@ -30,14 +30,12 @@ #include <memory> -#include "mongo/db/ops/modifier_interface.h" #include "mongo/unittest/unittest.h" namespace { using namespace mongo::modifiertable; -using mongo::ModifierInterface; using std::unique_ptr; TEST(getType, Normal) { @@ -46,14 +44,4 @@ TEST(getType, Normal) { ASSERT_EQUALS(getType("NotAModExpression"), MOD_UNKNOWN); } -TEST(makeUpdateMod, Normal) { - unique_ptr<ModifierInterface> mod; - - mod.reset(makeUpdateMod(MOD_SET)); - ASSERT_NOT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0)); - - mod.reset(makeUpdateMod(MOD_UNKNOWN)); - ASSERT_EQUALS(mod.get(), static_cast<ModifierInterface*>(0)); -} - } // unnamed namespace diff --git a/src/mongo/db/update/storage_validation.cpp b/src/mongo/db/update/storage_validation.cpp index 8b5be0d95e5..1ee11881029 100644 --- a/src/mongo/db/update/storage_validation.cpp +++ b/src/mongo/db/update/storage_validation.cpp @@ -164,25 +164,5 @@ void storageValid(mutablebson::ConstElement elem, const bool deep, std::uint32_t } } -std::uint32_t storageValidParents(mutablebson::ConstElement elem, std::uint32_t recursionLevel) { - uassert(ErrorCodes::Overflow, - str::stream() << "Document exceeds maximum nesting depth of " - << BSONDepth::getMaxDepthForUserStorage(), - recursionLevel <= BSONDepth::getMaxDepthForUserStorage()); - - auto root = elem.getDocument().root(); - if (elem != root) { - auto parent = elem.parent(); - if (parent.ok() && parent != root) { - const bool doRecursiveCheck = false; - const uint32_t parentsRecursionLevel = 0; - storageValid(parent, doRecursiveCheck, parentsRecursionLevel); - return storageValidParents(parent, recursionLevel + 1); - } - return recursionLevel + 1; - } - return recursionLevel; -} - } // namespace storage_validation } // namespace mongo diff --git a/src/mongo/db/update/storage_validation.h b/src/mongo/db/update/storage_validation.h index d47197e8d17..33f0a514a77 100644 --- a/src/mongo/db/update/storage_validation.h +++ b/src/mongo/db/update/storage_validation.h @@ -48,14 +48,6 @@ void storageValid(const mutablebson::Document& doc); */ void storageValid(mutablebson::ConstElement elem, const bool deep, std::uint32_t recursionLevel); -/** - * Checks that all of the parents of the MutableBSON element 'elem' are valid for storage. Note that - * 'elem' must be in a valid state when using this function. Uasserts if the validation fails, or if - * 'recursionLevel' exceeds the maximum allowable depth. On success, an integer is returned that - * represents the number of steps from this element to the root through ancestor nodes. - */ -std::uint32_t storageValidParents(mutablebson::ConstElement elem, std::uint32_t recursionLevel); - } // namespace storage_validation } // namespace mongo diff --git a/src/mongo/db/update/update_driver.cpp b/src/mongo/db/update/update_driver.cpp index 88d80341981..ee480f5e5d1 100644 --- a/src/mongo/db/update/update_driver.cpp +++ b/src/mongo/db/update/update_driver.cpp @@ -67,9 +67,9 @@ StatusWith<UpdateSemantics> updateSemanticsFromElement(BSONElement element) { auto updateSemantics = element.numberLong(); - if (updateSemantics < 0 || - updateSemantics >= static_cast<int>(UpdateSemantics::kNumUpdateSemantics)) { - return {ErrorCodes::BadValue, + // As of 3.7, we only support one version of the update language. + if (updateSemantics != static_cast<int>(UpdateSemantics::kUpdateNode)) { + return {ErrorCodes::Error(50659), str::stream() << "Unrecognized value for '$v' (UpdateSemantics) field: " << updateSemantics}; } @@ -147,23 +147,14 @@ bool parseUpdateExpression( } // namespace -UpdateDriver::UpdateDriver(const Options& opts) - : _replacementMode(false), - _indexedFields(NULL), - _logOp(opts.logOp), - _modOptions(opts.modOptions), - _affectIndices(false), - _positional(false) {} - -UpdateDriver::~UpdateDriver() { - clear(); -} +UpdateDriver::UpdateDriver(const boost::intrusive_ptr<ExpressionContext>& expCtx) + : _expCtx(expCtx) {} Status UpdateDriver::parse( const BSONObj& updateExpr, const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>& arrayFilters, const bool multi) { - clear(); + invariant(!_root && !_replacementMode, "Multiple calls to parse() on same UpdateDriver"); // Check if the update expression is a full object replacement. if (isDocReplacement(updateExpr)) { @@ -182,99 +173,24 @@ Status UpdateDriver::parse( // Register the fact that this driver is not doing a full object replacement. _replacementMode = false; - // Decide which update semantics to used, using the criteria outlined in the comment above this - // function's declaration. + // Some versions of mongod support more than one version of the update language and look for a + // $v "UpdateSemantics" field when applying an oplog entry, in order to know which version of + // the update language to apply with. We currently only support the 'kUpdateNode' version, but + // we parse $v and check its value for compatibility. BSONElement updateSemanticsElement = updateExpr[LogBuilder::kUpdateSemanticsFieldName]; - UpdateSemantics updateSemantics; if (updateSemanticsElement) { - if (!_modOptions.fromOplogApplication) { + if (!_fromOplogApplication) { return {ErrorCodes::FailedToParse, "The $v update field is only recognized internally"}; } auto statusWithUpdateSemantics = updateSemanticsFromElement(updateSemanticsElement); if (!statusWithUpdateSemantics.isOK()) { return statusWithUpdateSemantics.getStatus(); } - - updateSemantics = statusWithUpdateSemantics.getValue(); - } else if (_modOptions.fromOplogApplication) { - updateSemantics = UpdateSemantics::kModifierInterface; - } else { - updateSemantics = (serverGlobalParams.featureCompatibility.getVersion() != - ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo36) - ? UpdateSemantics::kModifierInterface - : UpdateSemantics::kUpdateNode; - } - - switch (updateSemantics) { - case UpdateSemantics::kModifierInterface: { - uassert(ErrorCodes::InvalidOptions, - str::stream() - << "The featureCompatibilityVersion must be 3.6 to use arrayFilters. See " - << feature_compatibility_version::kDochubLink - << ".", - arrayFilters.empty()); - bool foundUpdateSemanticsField = false; - for (auto&& mod : updateExpr) { - // If there is a "$v" field among the modifiers, we have already used it to - // determine that this is the correct parsing code. - if (mod.fieldNameStringData() == LogBuilder::kUpdateSemanticsFieldName) { - uassert(ErrorCodes::BadValue, - "Duplicate $v in oplog update document", - !foundUpdateSemanticsField); - foundUpdateSemanticsField = true; - invariant(mod.numberLong() == - static_cast<long long>(UpdateSemantics::kModifierInterface)); - continue; - } - - auto modType = validateMod(mod); - for (auto&& field : mod.Obj()) { - auto status = addAndParse(modType, field); - if (!status.isOK()) { - return status; - } - } - } - break; - } - case UpdateSemantics::kUpdateNode: { - auto root = stdx::make_unique<UpdateObjectNode>(); - _positional = - parseUpdateExpression(updateExpr, root.get(), _modOptions.expCtx, arrayFilters); - _root = std::move(root); - break; - } - default: - MONGO_UNREACHABLE; - } - - return Status::OK(); -} - -inline Status UpdateDriver::addAndParse(const modifiertable::ModifierType type, - const BSONElement& elem) { - if (elem.eoo()) { - return Status(ErrorCodes::FailedToParse, - str::stream() << "'" << elem.fieldName() << "' has no value in : " << elem - << " which is not allowed for any $" - << type - << " mod."); } - unique_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(type)); - dassert(mod.get()); - - bool positional = false; - Status status = mod->init(elem, _modOptions, &positional); - if (!status.isOK()) { - return status; - } - - // If any modifier indicates that it requires a positional match, toggle the - // _positional flag to true. - _positional = _positional || positional; - - _mods.push_back(mod.release()); + auto root = stdx::make_unique<UpdateObjectNode>(); + _positional = parseUpdateExpression(updateExpr, root.get(), _expCtx, arrayFilters); + _root = std::move(root); return Status::OK(); } @@ -330,7 +246,6 @@ Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery& query } Status UpdateDriver::update(StringData matchedField, - BSONObj original, mutablebson::Document* doc, bool validateForStorage, const FieldRefSet& immutablePaths, @@ -343,192 +258,35 @@ Status UpdateDriver::update(StringData matchedField, _logDoc.reset(); LogBuilder logBuilder(_logDoc.root()); - if (_root) { - - // We parsed using the new UpdateNode implementation. - UpdateNode::ApplyParams applyParams(doc->root(), immutablePaths); - applyParams.matchedField = matchedField; - applyParams.insert = _insert; - applyParams.fromOplogApplication = _modOptions.fromOplogApplication; - applyParams.validateForStorage = validateForStorage; - applyParams.indexData = _indexedFields; - if (_logOp && logOpRec) { - applyParams.logBuilder = &logBuilder; - } - auto applyResult = _root->apply(applyParams); - if (applyResult.indexesAffected) { - _affectIndices = true; - doc->disableInPlaceUpdates(); - } - if (docWasModified) { - *docWasModified = !applyResult.noop; - } - if (!_replacementMode && _logOp && logOpRec) { - // When using kUpdateNode update semantics on the primary, we must include a "$v" field - // in the update document so that the secondary knows to apply the update with - // kUpdateNode semantics. If this is a full document replacement, we don't need to - // specify the semantics (and there would be no place to put a "$v" field in the update - // document). - invariantOK(logBuilder.setUpdateSemantics(UpdateSemantics::kUpdateNode)); - } - - } else { - - // We parsed using the old ModifierInterface implementation. - // Ask each of the mods to type check whether they can operate over the current document - // and, if so, to change that document accordingly. - FieldRefSet updatedPaths; - - for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) { - ModifierInterface::ExecInfo execInfo; - Status status = (*it)->prepare(doc->root(), matchedField, &execInfo); - if (!status.isOK()) { - return status; - } - - if (execInfo.context == ModifierInterface::ExecInfo::INSERT_CONTEXT && !_insert) { - continue; - } - - - // Gather which fields this mod is interested on and whether these fields were - // "taken" by previous mods. Note that not all mods are multi-field mods. When we - // see an empty field, we may stop looking for others. - for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) { - if (execInfo.fieldRef[i] == 0) { - break; - } - - // Record each field being updated but check for conflicts first - const FieldRef* other; - if (!updatedPaths.insert(execInfo.fieldRef[i], &other)) { - return Status(ErrorCodes::ConflictingUpdateOperators, - str::stream() << "Cannot update '" << other->dottedField() - << "' and '" - << execInfo.fieldRef[i]->dottedField() - << "' at the same time"); - } - - // We start with the expectation that a mod will be in-place. But if the mod - // touched an indexed field and the mod will indeed be executed -- that is, it - // is not a no-op and it is in a valid context -- then we switch back to a - // non-in-place mode. - // - // To determine if indexes are affected: If we did not create a new element in an - // array, check whether the full path affects indexes. If we did create a new - // element in an array, check whether the array itself might affect any indexes. - // This is necessary because if there is an index {"a.b": 1}, and we set "a.1.c" and - // implicitly create an array element in "a", then we may need to add a null key to - // the index {"a.b": 1}, even though "a.1.c" does not appear to affect the index. - if (!_affectIndices && !execInfo.noOp && _indexedFields) { - auto pathLengthForIndexCheck = execInfo.indexOfArrayWithNewElement[i] - ? *execInfo.indexOfArrayWithNewElement[i] + 1 - : execInfo.fieldRef[i]->numParts(); - if (_indexedFields->mightBeIndexed( - execInfo.fieldRef[i]->dottedSubstring(0, pathLengthForIndexCheck))) { - _affectIndices = true; - doc->disableInPlaceUpdates(); - } - } - } - - if (!execInfo.noOp) { - status = (*it)->apply(); - - if (docWasModified) - *docWasModified = true; - - if (!status.isOK()) { - return status; - } - } - - // If we require a replication oplog entry for this update, go ahead and generate one. - if (!execInfo.noOp && _logOp && logOpRec) { - status = (*it)->log(&logBuilder); - if (!status.isOK()) { - return status; - } - } - } - - // Check for BSON depth and DBRef constraint violations. - if (validateForStorage) { - for (auto path = updatedPaths.begin(); path != updatedPaths.end(); ++path) { - - // Find the updated field in the updated document. - auto newElem = doc->root(); - for (size_t i = 0; i < (*path)->numParts(); ++i) { - if (newElem.getType() == BSONType::Array) { - auto indexFromField = parseUnsignedBase10Integer((*path)->getPart(i)); - if (indexFromField) { - newElem = newElem.findNthChild(*indexFromField); - } else { - newElem = newElem.getDocument().end(); - } - } else { - newElem = newElem[(*path)->getPart(i)]; - } - - if (!newElem.ok()) { - break; - } - } - - // newElem might be missing if $unset/$renamed-away. - if (newElem.ok()) { - - // Check parents. - const std::uint32_t recursionLevel = 0; - auto parentsDepth = - storage_validation::storageValidParents(newElem, recursionLevel); - - // Check element and its children. - const bool doRecursiveCheck = true; - storage_validation::storageValid(newElem, doRecursiveCheck, parentsDepth); - } - } - } - - for (auto path = immutablePaths.begin(); path != immutablePaths.end(); ++path) { - - if (!updatedPaths.findConflicts(*path, nullptr)) { - continue; - } - - // Find the updated field in the updated document. - auto newElem = doc->root(); - for (size_t i = 0; i < (*path)->numParts(); ++i) { - newElem = newElem[(*path)->getPart(i)]; - if (!newElem.ok()) { - break; - } - uassert(ErrorCodes::NotSingleValueField, - str::stream() - << "After applying the update to the document, the (immutable) field '" - << (*path)->dottedField() - << "' was found to be an array or array descendant.", - newElem.getType() != BSONType::Array); - } - - auto oldElem = - dotted_path_support::extractElementAtPath(original, (*path)->dottedField()); - - uassert(ErrorCodes::ImmutableField, - str::stream() << "After applying the update, the '" << (*path)->dottedField() - << "' (required and immutable) field was " - "found to have been removed --" - << original, - newElem.ok() || !oldElem.ok()); - if (newElem.ok() && oldElem.ok()) { - uassert(ErrorCodes::ImmutableField, - str::stream() << "After applying the update, the (immutable) field '" - << (*path)->dottedField() - << "' was found to have been altered to " - << newElem.toString(), - newElem.compareWithBSONElement(oldElem, nullptr, false) == 0); - } - } + UpdateNode::ApplyParams applyParams(doc->root(), immutablePaths); + applyParams.matchedField = matchedField; + applyParams.insert = _insert; + applyParams.fromOplogApplication = _fromOplogApplication; + applyParams.validateForStorage = validateForStorage; + applyParams.indexData = _indexedFields; + if (_logOp && logOpRec) { + applyParams.logBuilder = &logBuilder; + } + auto applyResult = _root->apply(applyParams); + if (applyResult.indexesAffected) { + _affectIndices = true; + doc->disableInPlaceUpdates(); + } + if (docWasModified) { + *docWasModified = !applyResult.noop; + } + if (!_replacementMode && _logOp && logOpRec) { + // If there are binVersion=3.6 mongod nodes in the replica set, they need to be told that + // this update is using the "kUpdateNode" version of the update semantics and not the older + // update semantics that could be used by a featureCompatibilityVersion=3.4 node. + // + // TODO (SERVER-32240): Once binVersion <= 3.6 nodes are not supported in a replica set, we + // can safely elide this "$v" UpdateSemantics field from oplog entries, because there will + // only one supported version, which all nodes will assume is in use. + // + // We also don't need to specify the semantics for a full document replacement (and there + // would be no place to put a "$v" field in the update document). + invariantOK(logBuilder.setUpdateSemantics(UpdateSemantics::kUpdateNode)); } if (_logOp && logOpRec) @@ -537,10 +295,6 @@ Status UpdateDriver::update(StringData matchedField, return Status::OK(); } -size_t UpdateDriver::numMods() const { - return _mods.size(); -} - bool UpdateDriver::isDocReplacement() const { return _replacementMode; } @@ -561,12 +315,12 @@ void UpdateDriver::setLogOp(bool logOp) { _logOp = logOp; } -ModifierInterface::Options UpdateDriver::modOptions() const { - return _modOptions; +bool UpdateDriver::fromOplogApplication() const { + return _fromOplogApplication; } -void UpdateDriver::setModOptions(ModifierInterface::Options modOpts) { - _modOptions = modOpts; +void UpdateDriver::setFromOplogApplication(bool fromOplogApplication) { + _fromOplogApplication = fromOplogApplication; } void UpdateDriver::setCollator(const CollatorInterface* collator) { @@ -574,21 +328,7 @@ void UpdateDriver::setCollator(const CollatorInterface* collator) { _root->setCollator(collator); } - for (auto&& mod : _mods) { - mod->setCollator(collator); - } - - _modOptions.expCtx->setCollator(collator); -} - -void UpdateDriver::clear() { - for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) { - delete *it; - } - _mods.clear(); - _indexedFields = NULL; - _replacementMode = false; - _positional = false; + _expCtx->setCollator(collator); } bool UpdateDriver::isDocReplacement(const BSONObj& updateExpr) { diff --git a/src/mongo/db/update/update_driver.h b/src/mongo/db/update/update_driver.h index e551d5fa2e8..d754a4f58f4 100644 --- a/src/mongo/db/update/update_driver.h +++ b/src/mongo/db/update/update_driver.h @@ -36,7 +36,6 @@ #include "mongo/bson/mutable/document.h" #include "mongo/db/field_ref_set.h" #include "mongo/db/jsobj.h" -#include "mongo/db/ops/modifier_interface.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/update/modifier_table.h" #include "mongo/db/update/update_object_node.h" @@ -49,25 +48,11 @@ class OperationContext; class UpdateDriver { public: - struct Options; - UpdateDriver(const Options& opts); - - ~UpdateDriver(); + UpdateDriver(const boost::intrusive_ptr<ExpressionContext>& expCtx); /** - * Parses the 'updateExpr' update expression. When parsing with the kUpdateNode update - * semantics, 'updateExpr' is parsed into the '_root' member variable, and when parsing with the - * kModifierInterface update semantics, 'updateExpr' is parsed into the '_mods' member variable. - * It is important that the secondary applies updates with the same semantics that the primary - * used. - * - The primary can add a "$v" field with an integer value (storing on UpdateSemantics enum - * value) that this function will use to determine which update semantics to use. - * - When applying an oplog entry that has no "$v" field, this function assumes - * kModifierInterface semantics. - * - When applying an update on the primary, this function uses the feature compatibility - * version to determine which update semantics to use. - * Uasserts if the featureCompatibilityVersion is 3.4 and 'arrayFilters' is non-empty. Uasserts - * or returns a non-ok status if 'updateExpr' fails to parse. + * Parses the 'updateExpr' update expression into the '_root' member variable. Uasserts or + * returns a non-ok status if 'updateExpr' fails to parse. */ Status parse( const BSONObj& updateExpr, @@ -108,11 +93,9 @@ public: * * If 'validateForStorage' is true, ensures that modified elements do not violate depth or DBRef * constraints. Ensures that no paths in 'immutablePaths' are modified (though they may be - * created, if they do not yet exist). The original document 'original' is needed for checking - * immutable paths for the ModifierInterface implementation. + * created, if they do not yet exist). */ Status update(StringData matchedField, - BSONObj original, mutablebson::Document* doc, bool validateForStorage, const FieldRefSet& immutablePaths, @@ -123,8 +106,6 @@ public: // Accessors // - size_t numMods() const; - bool isDocReplacement() const; static bool isDocReplacement(const BSONObj& updateExpr); @@ -134,8 +115,8 @@ public: bool logOp() const; void setLogOp(bool logOp); - ModifierInterface::Options modOptions() const; - void setModOptions(ModifierInterface::Options modOpts); + bool fromOplogApplication() const; + void setFromOplogApplication(bool fromOplogApplication); void setInsert(bool insert) { _insert = insert; @@ -161,9 +142,6 @@ public: void setCollator(const CollatorInterface* collator); private: - /** Resets the state of the class associated with mods (not the error state) */ - void clear(); - /** Create the modifier and add it to the back of the modifiers vector */ inline Status addAndParse(const modifiertable::ModifierType type, const BSONElement& elem); @@ -171,39 +149,37 @@ private: // immutable properties after parsing // - // Is there a list of $mod's on '_mods' or is it just full object replacement? - bool _replacementMode; + // Is this a full object replacement or do we have update modifiers in the '_root' UpdateNode + // tree? + bool _replacementMode = false; - // The root of the UpdateNode tree. If the featureCompatibilityVersion is 3.6, the update - // expression is parsed into '_root'. + // The root of the UpdateNode tree. std::unique_ptr<UpdateNode> _root; - // Collection of update mod instances. Owned here. If the featureCompatibilityVersion is 3.4, - // the update expression is parsed into '_mods'. - std::vector<ModifierInterface*> _mods; - // What are the list of fields in the collection over which the update is going to be // applied that participate in indices? // // NOTE: Owned by the collection's info cache!. - const UpdateIndexData* _indexedFields; + const UpdateIndexData* _indexedFields = nullptr; // // mutable properties after parsing // // Should this driver generate an oplog record when it applies the update? - bool _logOp; + bool _logOp = false; - // The options to initiate the mods with - ModifierInterface::Options _modOptions; + // True if this update comes from an oplog application. + bool _fromOplogApplication = false; + + boost::intrusive_ptr<ExpressionContext> _expCtx; // Are any of the fields mentioned in the mods participating in any index? Is set anew // at each call to update. - bool _affectIndices; + bool _affectIndices = false; // Do any of the mods require positional match details when calling 'prepare'? - bool _positional; + bool _positional = false; // Is this update going to be an upsert? bool _insert = false; @@ -215,12 +191,4 @@ private: mutablebson::Document _logDoc; }; -struct UpdateDriver::Options { - bool logOp; - ModifierInterface::Options modOptions; - - explicit Options(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : logOp(false), modOptions(ModifierInterface::Options::normal(expCtx)) {} -}; - } // namespace mongo diff --git a/src/mongo/db/update/update_driver_test.cpp b/src/mongo/db/update/update_driver_test.cpp index f64c1e353d3..1911c417f0a 100644 --- a/src/mongo/db/update/update_driver_test.cpp +++ b/src/mongo/db/update/update_driver_test.cpp @@ -52,38 +52,31 @@ using mongoutils::str::stream; TEST(Parse, Normal) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_OK(driver.parse(fromjson("{$set:{a:1}}"), arrayFilters)); - ASSERT_EQUALS(driver.numMods(), 1U); ASSERT_FALSE(driver.isDocReplacement()); } TEST(Parse, MultiMods) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_OK(driver.parse(fromjson("{$set:{a:1, b:1}}"), arrayFilters)); - ASSERT_EQUALS(driver.numMods(), 2U); ASSERT_FALSE(driver.isDocReplacement()); } TEST(Parse, MixingMods) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_OK(driver.parse(fromjson("{$set:{a:1}, $unset:{b:1}}"), arrayFilters)); - ASSERT_EQUALS(driver.numMods(), 2U); ASSERT_FALSE(driver.isDocReplacement()); } TEST(Parse, ObjectReplacment) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_OK(driver.parse(fromjson("{obj: \"obj replacement\"}"), arrayFilters)); ASSERT_TRUE(driver.isDocReplacement()); @@ -91,8 +84,7 @@ TEST(Parse, ObjectReplacment) { TEST(Parse, EmptyMod) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_THROWS_CODE_AND_WHAT( driver.parse(fromjson("{$set:{}}"), arrayFilters).transitional_ignore(), @@ -103,8 +95,7 @@ TEST(Parse, EmptyMod) { TEST(Parse, WrongMod) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_THROWS_CODE_AND_WHAT( driver.parse(fromjson("{$xyz:{a:1}}"), arrayFilters).transitional_ignore(), @@ -115,8 +106,7 @@ TEST(Parse, WrongMod) { TEST(Parse, WrongType) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_THROWS_CODE_AND_WHAT( driver.parse(fromjson("{$set:[{a:1}]}"), arrayFilters).transitional_ignore(), @@ -128,8 +118,7 @@ TEST(Parse, WrongType) { TEST(Parse, ModsWithLaterObjReplacement) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_THROWS_CODE_AND_WHAT( driver.parse(fromjson("{$set:{a:1}, obj: \"obj replacement\"}"), arrayFilters) @@ -141,11 +130,9 @@ TEST(Parse, ModsWithLaterObjReplacement) { TEST(Parse, SetOnInsert) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_OK(driver.parse(fromjson("{$setOnInsert:{a:1}}"), arrayFilters)); - ASSERT_EQUALS(driver.numMods(), 1U); ASSERT_FALSE(driver.isDocReplacement()); } @@ -153,27 +140,17 @@ TEST(Collator, SetCollationUpdatesModifierInterfaces) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); CollatorInterfaceMock reverseStringCollator(CollatorInterfaceMock::MockType::kReverseString); BSONObj updateDocument = fromjson("{$max: {a: 'abd'}}"); - UpdateDriver::Options opts(expCtx); - UpdateDriver driver(opts); + UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_OK(driver.parse(updateDocument, arrayFilters)); - ASSERT_EQUALS(driver.numMods(), 1U); - const BSONObj original; const bool validateForStorage = true; const FieldRefSet emptyImmutablePaths; bool modified = false; mutablebson::Document doc(fromjson("{a: 'cba'}")); driver.setCollator(&reverseStringCollator); - driver - .update(StringData(), - original, - &doc, - validateForStorage, - emptyImmutablePaths, - nullptr, - &modified) + driver.update(StringData(), &doc, validateForStorage, emptyImmutablePaths, nullptr, &modified) .transitional_ignore(); ASSERT_TRUE(modified); @@ -190,10 +167,8 @@ class CreateFromQueryFixture : public mongo::unittest::Test { public: CreateFromQueryFixture() : _opCtx(_serviceContext.makeOperationContext()), - _driverOps(new UpdateDriver( - UpdateDriver::Options(new ExpressionContext(_opCtx.get(), nullptr)))), - _driverRepl(new UpdateDriver( - UpdateDriver::Options(new ExpressionContext(_opCtx.get(), nullptr)))) { + _driverOps(new UpdateDriver(new ExpressionContext(_opCtx.get(), nullptr))), + _driverRepl(new UpdateDriver(new ExpressionContext(_opCtx.get(), nullptr))) { _driverOps->parse(fromjson("{$set:{'_':1}}"), _arrayFilters).transitional_ignore(); _driverRepl->parse(fromjson("{}"), _arrayFilters).transitional_ignore(); } diff --git a/src/mongo/dbtests/query_stage_update.cpp b/src/mongo/dbtests/query_stage_update.cpp index d53d3a2daf4..cc9cc693e34 100644 --- a/src/mongo/dbtests/query_stage_update.cpp +++ b/src/mongo/dbtests/query_stage_update.cpp @@ -190,7 +190,7 @@ public: CurOp& curOp = *CurOp::get(_opCtx); OpDebug* opDebug = &curOp.debug(); const CollatorInterface* collator = nullptr; - UpdateDriver driver((UpdateDriver::Options(new ExpressionContext(&_opCtx, collator)))); + UpdateDriver driver(new ExpressionContext(&_opCtx, collator)); Collection* collection = ctx.getCollection(); // Collection should be empty. @@ -261,7 +261,7 @@ public: CurOp& curOp = *CurOp::get(_opCtx); OpDebug* opDebug = &curOp.debug(); const CollatorInterface* collator = nullptr; - UpdateDriver driver((UpdateDriver::Options(new ExpressionContext(&_opCtx, collator)))); + UpdateDriver driver(new ExpressionContext(&_opCtx, collator)); Database* db = ctx.db(); Collection* coll = db->getCollection(&_opCtx, nss); @@ -380,7 +380,7 @@ public: UpdateLifecycleImpl updateLifecycle(nss); UpdateRequest request(nss); const CollatorInterface* collator = nullptr; - UpdateDriver driver((UpdateDriver::Options(new ExpressionContext(&_opCtx, collator)))); + UpdateDriver driver(new ExpressionContext(&_opCtx, collator)); const int targetDocIndex = 0; // We'll be working with the first doc in the collection. const BSONObj query = BSON("foo" << BSON("$gte" << targetDocIndex)); const auto ws = make_unique<WorkingSet>(); @@ -471,7 +471,7 @@ public: UpdateLifecycleImpl updateLifecycle(nss); UpdateRequest request(nss); const CollatorInterface* collator = nullptr; - UpdateDriver driver((UpdateDriver::Options(new ExpressionContext(&_opCtx, collator)))); + UpdateDriver driver(new ExpressionContext(&_opCtx, collator)); const int targetDocIndex = 10; const BSONObj query = BSON("foo" << BSON("$gte" << targetDocIndex)); const auto ws = make_unique<WorkingSet>(); @@ -558,7 +558,7 @@ public: UpdateLifecycleImpl updateLifecycle(nss); UpdateRequest request(nss); const CollatorInterface* collator = nullptr; - UpdateDriver driver((UpdateDriver::Options(new ExpressionContext(&_opCtx, collator)))); + UpdateDriver driver(new ExpressionContext(&_opCtx, collator)); const BSONObj query = BSONObj(); const auto ws = make_unique<WorkingSet>(); const unique_ptr<CanonicalQuery> cq(canonicalize(query)); |