diff options
author | Justin Seyster <justin.seyster@mongodb.com> | 2017-12-14 15:21:10 -0500 |
---|---|---|
committer | Justin Seyster <justin.seyster@mongodb.com> | 2017-12-14 15:37:19 -0500 |
commit | 3b116b0dc632a0533c8b76ddbf02186e4bf6774e (patch) | |
tree | 2f57de2f0df1acac0e0c9cae85b492b7b8ec3216 /src/mongo/db | |
parent | c0ebce4abf9b0cfd4271767fa062ea2d5b486554 (diff) | |
download | mongo-3b116b0dc632a0533c8b76ddbf02186e4bf6774e.tar.gz |
SERVER-30854 Remove ModifierInterface update code.
We left the deleted update system in 3.6 to support upgrades from 3.4,
but newer versions will always use the new UpdateNode update system.
Fun fact: this commit deletes more lines than were inserted by the
previous 100 commits.
Diffstat (limited to 'src/mongo/db')
54 files changed, 117 insertions, 13055 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(); } |