diff options
Diffstat (limited to 'src')
79 files changed, 1825 insertions, 897 deletions
diff --git a/src/mongo/db/audit.cpp b/src/mongo/db/audit.cpp index add615a129e..81bb38b720d 100644 --- a/src/mongo/db/audit.cpp +++ b/src/mongo/db/audit.cpp @@ -69,7 +69,7 @@ void mongo::audit::logQueryAuthzCheck(Client* client, void mongo::audit::logUpdateAuthzCheck(Client* client, const NamespaceString& ns, const BSONObj& query, - const BSONObj& updateObj, + const write_ops::UpdateModification& update, bool isUpsert, bool isMulti, ErrorCodes::Error result) {} diff --git a/src/mongo/db/audit.h b/src/mongo/db/audit.h index 9fb28a707bf..d73a9101367 100644 --- a/src/mongo/db/audit.h +++ b/src/mongo/db/audit.h @@ -37,6 +37,7 @@ #include "mongo/base/error_codes.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/user.h" +#include "mongo/db/ops/write_ops_parsers.h" #include "mongo/rpc/op_msg.h" namespace mongo { @@ -137,7 +138,7 @@ void logQueryAuthzCheck(Client* client, void logUpdateAuthzCheck(Client* client, const NamespaceString& ns, const BSONObj& query, - const BSONObj& updateObj, + const write_ops::UpdateModification& update, bool isUpsert, bool isMulti, ErrorCodes::Error result); diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 0c4bc0d4f4a..22a9bf952b7 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -43,6 +43,7 @@ #include "mongo/db/auth/user_set.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" +#include "mongo/db/ops/write_ops_parsers.h" namespace mongo { @@ -203,7 +204,7 @@ public: virtual Status checkAuthForUpdate(OperationContext* opCtx, const NamespaceString& ns, const BSONObj& query, - const BSONObj& update, + const write_ops::UpdateModification& update, bool upsert) = 0; // Checks if this connection has the privileges necessary to insert to the given namespace. diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index ed69d130ecf..fd186c06d7d 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -364,7 +364,7 @@ Status AuthorizationSessionImpl::checkAuthForInsert(OperationContext* opCtx, Status AuthorizationSessionImpl::checkAuthForUpdate(OperationContext* opCtx, const NamespaceString& ns, const BSONObj& query, - const BSONObj& update, + const write_ops::UpdateModification& update, bool upsert) { ActionSet required{ActionType::update}; StringData operationType = "update"_sd; diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index 5820052f811..ad8bdb1942c 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -110,7 +110,7 @@ public: Status checkAuthForUpdate(OperationContext* opCtx, const NamespaceString& ns, const BSONObj& query, - const BSONObj& update, + const write_ops::UpdateModification& update, bool upsert) override; Status checkAuthForInsert(OperationContext* opCtx, const NamespaceString& ns) override; diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index ae25da086c8..aa091e04919 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -110,7 +110,7 @@ void makeUpdateRequest(const OperationContext* opCtx, UpdateRequest* requestOut) { requestOut->setQuery(args.getQuery()); requestOut->setProj(args.getFields()); - requestOut->setUpdates(args.getUpdateObj()); + requestOut->setUpdateModification(args.getUpdateObj()); requestOut->setSort(args.getSort()); requestOut->setCollation(args.getCollation()); requestOut->setArrayFilters(args.getArrayFilters()); diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp index f480dda2886..022bfe5a0ef 100644 --- a/src/mongo/db/commands/write_commands/write_commands.cpp +++ b/src/mongo/db/commands/write_commands/write_commands.cpp @@ -361,7 +361,7 @@ private: UpdateRequest updateRequest(_batch.getNamespace()); updateRequest.setQuery(_batch.getUpdates()[0].getQ()); - updateRequest.setUpdates(_batch.getUpdates()[0].getU()); + updateRequest.setUpdateModification(_batch.getUpdates()[0].getU()); updateRequest.setCollation(write_ops::collationOf(_batch.getUpdates()[0])); updateRequest.setArrayFilters(write_ops::arrayFiltersOf(_batch.getUpdates()[0])); updateRequest.setMulti(_batch.getUpdates()[0].getMulti()); diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index 30936712067..ea7280d114e 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -222,7 +222,7 @@ void Helpers::upsert(OperationContext* opCtx, UpdateRequest request(requestNs); request.setQuery(id); - request.setUpdates(o); + request.setUpdateModification(o); request.setUpsert(); request.setFromMigration(fromMigrate); @@ -235,7 +235,7 @@ void Helpers::putSingleton(OperationContext* opCtx, const char* ns, BSONObj obj) const NamespaceString requestNs(ns); UpdateRequest request(requestNs); - request.setUpdates(obj); + request.setUpdateModification(obj); request.setUpsert(); update(opCtx, context.db(), request); diff --git a/src/mongo/db/exec/plan_stats.h b/src/mongo/db/exec/plan_stats.h index da1f5b998d1..527adc44bca 100644 --- a/src/mongo/db/exec/plan_stats.h +++ b/src/mongo/db/exec/plan_stats.h @@ -560,11 +560,7 @@ struct NearStats : public SpecificStats { struct UpdateStats : public SpecificStats { UpdateStats() - : nMatched(0), - nModified(0), - isDocReplacement(false), - fastmodinsert(false), - inserted(false) {} + : nMatched(0), nModified(0), isModUpdate(false), fastmodinsert(false), inserted(false) {} SpecificStats* clone() const final { return new UpdateStats(*this); @@ -576,8 +572,8 @@ struct UpdateStats : public SpecificStats { // The number of documents modified by this update. size_t nModified; - // True iff this is a doc-replacement style update, as opposed to a $mod update. - bool isDocReplacement; + // True iff this is a $mod update. + bool isModUpdate; // A 'fastmodinsert' is an insert resulting from an {upsert: true} update // which is a doc-replacement style update. It's "fast" because we don't need diff --git a/src/mongo/db/exec/update_stage.cpp b/src/mongo/db/exec/update_stage.cpp index 1245a7f0a66..c6344434983 100644 --- a/src/mongo/db/exec/update_stage.cpp +++ b/src/mongo/db/exec/update_stage.cpp @@ -178,9 +178,7 @@ UpdateStage::UpdateStage(OperationContext* opCtx, !(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(); + _specificStats.isModUpdate = params.driver->type() == UpdateDriver::UpdateType::kOperator; } BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj, RecordId& recordId) { @@ -417,7 +415,7 @@ BSONObj UpdateStage::applyUpdateOpsForInsert(OperationContext* opCtx, } requiredPaths.keepShortest(&idFieldRef); uassertStatusOK(driver->populateDocumentWithQueryFields(*cq, requiredPaths, *doc)); - if (driver->isDocReplacement()) + if (driver->type() == UpdateDriver::UpdateType::kReplacement) stats->fastmodinsert = true; } else { fassert(17354, CanonicalQuery::isSimpleIdQuery(query)); @@ -877,7 +875,7 @@ void UpdateStage::recordUpdateStatsInOpDebug(const UpdateStats* updateStats, OpD UpdateResult UpdateStage::makeUpdateResult(const UpdateStats* updateStats) { return UpdateResult(updateStats->nMatched > 0 /* Did we update at least one obj? */, - !updateStats->isDocReplacement /* $mod or obj replacement */, + updateStats->isModUpdate /* Is this a $mod update? */, updateStats->nModified /* number of modified docs, no no-ops */, updateStats->nMatched /* # of docs matched/updated, even no-ops */, updateStats->objInserted); diff --git a/src/mongo/db/ops/SConscript b/src/mongo/db/ops/SConscript index 5ded7b6a151..727eed3a62f 100644 --- a/src/mongo/db/ops/SConscript +++ b/src/mongo/db/ops/SConscript @@ -35,6 +35,7 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/dbmessage', + '$BUILD_DIR/mongo/db/pipeline/aggregation_request', '$BUILD_DIR/mongo/idl/idl_parser', ], ) diff --git a/src/mongo/db/ops/parsed_update.cpp b/src/mongo/db/ops/parsed_update.cpp index 6958425529b..c4b7f40c8ee 100644 --- a/src/mongo/db/ops/parsed_update.cpp +++ b/src/mongo/db/ops/parsed_update.cpp @@ -56,6 +56,12 @@ Status ParsedUpdate::parseRequest() { invariant(_request->getProj().isEmpty() || _request->shouldReturnAnyDocs()); if (!_request->getCollation().isEmpty()) { + // TODO SERVER-40398: Remove once collation is supported and tested for pipeline updates. + uassert(ErrorCodes::NotImplemented, + "Collation is not yet supported for pipeline-style updates", + _request->getUpdateModification().type() != + write_ops::UpdateModification::Type::kPipeline); + auto collator = CollatorFactoryInterface::get(_opCtx->getServiceContext()) ->makeFromBSON(_request->getCollation()); if (!collator.isOK()) { @@ -145,7 +151,7 @@ void ParsedUpdate::parseUpdate() { _driver.setLogOp(true); _driver.setFromOplogApplication(_request->isFromOplogApplication()); - _driver.parse(_request->getUpdates(), _arrayFilters, _request->isMulti()); + _driver.parse(_request->getUpdateModification(), _arrayFilters, _request->isMulti()); } StatusWith<std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>> diff --git a/src/mongo/db/ops/update_request.h b/src/mongo/db/ops/update_request.h index 00249500343..587fdb1b718 100644 --- a/src/mongo/db/ops/update_request.h +++ b/src/mongo/db/ops/update_request.h @@ -33,6 +33,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/logical_session_id.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/ops/write_ops_parsers.h" #include "mongo/db/query/explain.h" #include "mongo/util/str.h" @@ -101,12 +102,12 @@ public: return _collation; } - inline void setUpdates(const BSONObj& updates) { - _updates = updates; + inline void setUpdateModification(const write_ops::UpdateModification& updateMod) { + _updateMod = updateMod; } - inline const BSONObj& getUpdates() const { - return _updates; + inline const write_ops::UpdateModification& getUpdateModification() const { + return _updateMod; } inline void setArrayFilters(const std::vector<BSONObj>& arrayFilters) { @@ -206,7 +207,7 @@ public: builder << " projection: " << _proj; builder << " sort: " << _sort; builder << " collation: " << _collation; - builder << " updates: " << _updates; + builder << " updateModification: " << _updateMod.toString(); builder << " stmtId: " << _stmtId; builder << " arrayFilters: ["; @@ -245,7 +246,7 @@ private: BSONObj _collation; // Contains the modifiers to apply to matched objects, or a replacement document. - BSONObj _updates; + write_ops::UpdateModification _updateMod; // Filters to specify which array elements should be updated. std::vector<BSONObj> _arrayFilters; diff --git a/src/mongo/db/ops/write_ops.idl b/src/mongo/db/ops/write_ops.idl index 800688c4b04..c570b799b36 100644 --- a/src/mongo/db/ops/write_ops.idl +++ b/src/mongo/db/ops/write_ops.idl @@ -44,6 +44,14 @@ types: serializer: "::mongo::write_ops::writeMultiDeleteProperty" deserializer: "::mongo::write_ops::readMultiDeleteProperty" + update_modification: + bson_serialization_type: any + description: "Holds the contents of the update command 'u' field, describing the + modifications to apply on update." + cpp_type: "mongo::write_ops::UpdateModification" + serializer: "mongo::write_ops::UpdateModification::serializeToBSON" + deserializer: "mongo::write_ops::UpdateModification::parseFromBSON" + structs: WriteCommandBase: @@ -91,7 +99,7 @@ structs: type: object u: description: "Set of modifications to apply." - type: object + type: update_modification arrayFilters: description: "Specifies which array elements an update modifier should apply to." type: array<object> diff --git a/src/mongo/db/ops/write_ops_exec.cpp b/src/mongo/db/ops/write_ops_exec.cpp index e7165c3da34..cc6d2fcd041 100644 --- a/src/mongo/db/ops/write_ops_exec.cpp +++ b/src/mongo/db/ops/write_ops_exec.cpp @@ -672,7 +672,7 @@ static SingleWriteResult performSingleUpdateOpWithDupKeyRetry(OperationContext* UpdateRequest request(ns); request.setQuery(op.getQ()); - request.setUpdates(op.getU()); + request.setUpdateModification(op.getU()); request.setCollation(write_ops::collationOf(op)); request.setStmtId(stmtId); request.setArrayFilters(write_ops::arrayFiltersOf(op)); @@ -736,6 +736,20 @@ WriteResult performUpdates(OperationContext* opCtx, const write_ops::Update& who out.results.reserve(wholeOp.getUpdates().size()); for (auto&& singleOp : wholeOp.getUpdates()) { + if (singleOp.getU().type() == write_ops::UpdateModification::Type::kPipeline) { + // TODO SERVER-40400: Remove once bypassDocumentValidation is supported and tested for + // pipeline updates. + uassert(ErrorCodes::NotImplemented, + "bypassDocumentValidation is not yet supported for pipeline-style updates", + !wholeOp.getWriteCommandBase().getBypassDocumentValidation()); + + // TODO SERVER-40402: Remove once writeConcern is supported and tested for pipeline + // updates. + uassert(ErrorCodes::NotImplemented, + "writeConcern is not yet supported for pipeline-style updates", + opCtx->getWriteConcern().usedDefault); + } + const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++); if (opCtx->getTxnNumber()) { if (!txnParticipant.inMultiDocumentTransaction()) { diff --git a/src/mongo/db/ops/write_ops_parsers.cpp b/src/mongo/db/ops/write_ops_parsers.cpp index 7d6a03ec977..c15aafce0d0 100644 --- a/src/mongo/db/ops/write_ops_parsers.cpp +++ b/src/mongo/db/ops/write_ops_parsers.cpp @@ -31,8 +31,10 @@ #include "mongo/db/ops/write_ops_parsers.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/dbmessage.h" #include "mongo/db/ops/write_ops.h" +#include "mongo/db/pipeline/aggregation_request.h" #include "mongo/util/assert_util.h" #include "mongo/util/str.h" @@ -166,7 +168,8 @@ write_ops::Update UpdateOp::parseLegacy(const Message& msgRaw) { singleUpdate.setUpsert(flags & UpdateOption_Upsert); singleUpdate.setMulti(flags & UpdateOption_Multi); singleUpdate.setQ(msg.nextJsObj()); - singleUpdate.setU(msg.nextJsObj()); + singleUpdate.setU( + write_ops::UpdateModification::parseLegacyOpUpdateFromBSON(msg.nextJsObj())); return updates; }()); @@ -209,4 +212,52 @@ write_ops::Delete DeleteOp::parseLegacy(const Message& msgRaw) { return op; } +write_ops::UpdateModification::UpdateModification(BSONElement update) { + const auto type = update.type(); + if (type == BSONType::Object) { + _classicUpdate = update.Obj(); + _type = Type::kClassic; + return; + } + + uassert( + ErrorCodes::FailedToParse, "Update argument must be an object", getTestCommandsEnabled()); + + uassert(ErrorCodes::FailedToParse, + "Update argument must be either an object or an array", + type == BSONType::Array); + + _type = Type::kPipeline; + + _pipeline = uassertStatusOK(AggregationRequest::parsePipelineFromBSON(update)); +} + +write_ops::UpdateModification::UpdateModification(const BSONObj& update) { + _classicUpdate = update; + _type = Type::kClassic; +} + +write_ops::UpdateModification write_ops::UpdateModification::parseFromBSON(BSONElement elem) { + return UpdateModification(elem); +} + +write_ops::UpdateModification write_ops::UpdateModification::parseLegacyOpUpdateFromBSON( + const BSONObj& obj) { + return UpdateModification(obj); +} + +void write_ops::UpdateModification::serializeToBSON(StringData fieldName, + BSONObjBuilder* bob) const { + if (_type == Type::kClassic) { + *bob << fieldName << *_classicUpdate; + return; + } + + BSONArrayBuilder arrayBuilder(bob->subarrayStart(fieldName)); + for (auto&& stage : *_pipeline) { + arrayBuilder << stage; + } + arrayBuilder.doneFast(); +} + } // namespace mongo diff --git a/src/mongo/db/ops/write_ops_parsers.h b/src/mongo/db/ops/write_ops_parsers.h index fba5f5aa9b1..77ccb46eded 100644 --- a/src/mongo/db/ops/write_ops_parsers.h +++ b/src/mongo/db/ops/write_ops_parsers.h @@ -32,10 +32,16 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/pipeline/value.h" namespace mongo { namespace write_ops { +// Conservative per array element overhead. This value was calculated as 1 byte (element type) + 5 +// bytes (max string encoding of the array index encoded as string and the maximum key is 99999) + 1 +// byte (zero terminator) = 7 bytes +constexpr int kBSONArrayPerElementOverheadBytes = 7; + /** * Parses the 'limit' property of a delete entry, which has inverted meaning from the 'multi' * property of an update. @@ -47,5 +53,81 @@ bool readMultiDeleteProperty(const BSONElement& limitElement); */ void writeMultiDeleteProperty(bool isMulti, StringData fieldName, BSONObjBuilder* builder); +class UpdateModification { +public: + enum class Type { kClassic, kPipeline }; + + static StringData typeToString(Type type) { + return (type == Type::kClassic ? "Classic"_sd : "Pipeline"_sd); + } + + UpdateModification() = default; + UpdateModification(BSONElement update); + + // This constructor exists only to provide a fast-path for constructing classic-style updates. + UpdateModification(const BSONObj& update); + + + /** + * These methods support IDL parsing of the "u" field from the update command and OP_UPDATE. + */ + static UpdateModification parseFromBSON(BSONElement elem); + void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const; + + // When parsing from legacy OP_UPDATE messages, we receive the "u" field as an object. When an + // array is parsed, we receive it as an object with numeric fields names and can't differentiate + // between a user constructed object and an array. For that reason, we don't support pipeline + // style update via OP_UPDATE and 'obj' is assumed to be a classic update. + // + // If a user did send a pipeline-style update via OP_UPDATE, it would fail parsing a field + // representing an aggregation stage, due to the leading '$'' character. + static UpdateModification parseLegacyOpUpdateFromBSON(const BSONObj& obj); + + int objsize() const { + if (_type == Type::kClassic) { + return _classicUpdate->objsize(); + } + + int size = 0; + std::for_each(_pipeline->begin(), _pipeline->end(), [&size](const BSONObj& obj) { + size += obj.objsize() + kBSONArrayPerElementOverheadBytes; + }); + + return size + kBSONArrayPerElementOverheadBytes; + } + + Type type() const { + return _type; + } + + BSONObj getUpdateClassic() const { + invariant(_type == Type::kClassic); + return *_classicUpdate; + } + + const std::vector<BSONObj>& getUpdatePipeline() const { + invariant(_type == Type::kPipeline); + return *_pipeline; + } + + std::string toString() const { + StringBuilder sb; + sb << "{type: " << typeToString(_type) << ", update: "; + + if (_type == Type::kClassic) { + sb << *_classicUpdate << "}"; + } else { + sb << Value(*_pipeline).toString(); + } + + return sb.str(); + } + +private: + Type _type = Type::kClassic; + boost::optional<BSONObj> _classicUpdate; + boost::optional<std::vector<BSONObj>> _pipeline; +}; + } // namespace write_ops } // namespace mongo diff --git a/src/mongo/db/ops/write_ops_parsers_test.cpp b/src/mongo/db/ops/write_ops_parsers_test.cpp index e014a35b7d2..4c9b98d252b 100644 --- a/src/mongo/db/ops/write_ops_parsers_test.cpp +++ b/src/mongo/db/ops/write_ops_parsers_test.cpp @@ -30,6 +30,7 @@ #include "mongo/platform/basic.h" #include "mongo/db/catalog/document_validation.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/dbmessage.h" #include "mongo/db/ops/write_ops.h" #include "mongo/db/ops/write_ops_parsers.h" @@ -338,7 +339,11 @@ TEST(CommandWriteOpsParsers, Update) { ASSERT_EQ(op.getWriteCommandBase().getOrdered(), true); ASSERT_EQ(op.getUpdates().size(), 1u); ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getQ(), query); - ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getU(), update); + + const auto& updateMod = op.getUpdates()[0].getU(); + ASSERT(updateMod.type() == write_ops::UpdateModification::Type::kClassic); + ASSERT_BSONOBJ_EQ(updateMod.getUpdateClassic(), update); + ASSERT_BSONOBJ_EQ(write_ops::collationOf(op.getUpdates()[0]), collation); ASSERT_EQ(write_ops::arrayFiltersOf(op.getUpdates()[0]).size(), 1u); ASSERT_BSONOBJ_EQ(write_ops::arrayFiltersOf(op.getUpdates()[0]).front(), @@ -351,6 +356,46 @@ TEST(CommandWriteOpsParsers, Update) { } } +TEST(CommandWriteOpsParsers, UpdateWithPipeline) { + // TODO SERVER-40419: Remove 'setTestCommandsEnable(true)' for this test. + setTestCommandsEnabled(true); + const auto ns = NamespaceString("test", "foo"); + const BSONObj query = BSON("q" << BSON("x" << 1)); + std::vector<BSONObj> pipeline{BSON("$addFields" << BSON("x" << 1))}; + const BSONObj update = BSON("u" << pipeline); + const BSONObj collation = BSON("locale" + << "en_US"); + for (bool upsert : {false, true}) { + for (bool multi : {false, true}) { + auto rawUpdate = BSON( + "q" << query["q"] << "u" << update["u"] << "multi" << multi << "upsert" << upsert + << "collation" + << collation); + auto cmd = BSON("update" << ns.coll() << "updates" << BSON_ARRAY(rawUpdate)); + for (bool seq : {false, true}) { + auto request = toOpMsg(ns.db(), cmd, seq); + auto op = UpdateOp::parse(request); + ASSERT_EQ(op.getNamespace().ns(), ns.ns()); + ASSERT(!op.getWriteCommandBase().getBypassDocumentValidation()); + ASSERT_EQ(op.getWriteCommandBase().getOrdered(), true); + ASSERT_EQ(op.getUpdates().size(), 1u); + ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getQ(), query["q"].Obj()); + + const auto& updateMod = op.getUpdates()[0].getU(); + const auto& updateModPipeline = updateMod.getUpdatePipeline(); + ASSERT(updateMod.type() == write_ops::UpdateModification::Type::kPipeline); + ASSERT_EQ(updateModPipeline.size(), 1u); + ASSERT_BSONOBJ_EQ(updateModPipeline[0], pipeline[0]); + + ASSERT_BSONOBJ_EQ(write_ops::collationOf(op.getUpdates()[0]), collation); + ASSERT_EQ(op.getUpdates()[0].getUpsert(), upsert); + ASSERT_EQ(op.getUpdates()[0].getMulti(), multi); + ASSERT_BSONOBJ_EQ(op.getUpdates()[0].toBSON(), rawUpdate); + } + } + } +} + TEST(CommandWriteOpsParsers, Remove) { const auto ns = NamespaceString("test", "foo"); const BSONObj query = BSON("x" << 1); @@ -451,7 +496,37 @@ TEST(LegacyWriteOpsParsers, Update) { ASSERT_EQ(op.getWriteCommandBase().getOrdered(), true); ASSERT_EQ(op.getUpdates().size(), 1u); ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getQ(), query); - ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getU(), update); + ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getU().getUpdateClassic(), update); + ASSERT_EQ(op.getUpdates()[0].getUpsert(), upsert); + ASSERT_EQ(op.getUpdates()[0].getMulti(), multi); + } + } +} + +// When parsing from legacy OP_UPDATE messages, we receive the "u" field as an object. When an array +// is parsed, we receive it as an object with numeric fields names and can't differentiate between a +// user constructed object and an array. For that reason, we parse as a classic-style update rather +// than as pipeline-style. +TEST(LegacyWriteOpsParsers, UpdateWithArrayUpdateFieldIsParsedAsReplacementStyleUpdate) { + const std::string ns = "test.foo"; + const BSONObj query = BSON("x" << 1); + const BSONObj update = BSON_ARRAY(BSON("$addFields" << BSON("x" << 1))); + for (bool upsert : {false, true}) { + for (bool multi : {false, true}) { + auto message = makeUpdateMessage(ns, + query, + update, + (upsert ? UpdateOption_Upsert : 0) | + (multi ? UpdateOption_Multi : 0)); + const auto op = UpdateOp::parseLegacy(message); + ASSERT_EQ(op.getNamespace().ns(), ns); + ASSERT(!op.getWriteCommandBase().getBypassDocumentValidation()); + ASSERT_EQ(op.getWriteCommandBase().getOrdered(), true); + ASSERT_EQ(op.getUpdates().size(), 1u); + ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getQ(), query); + ASSERT(op.getUpdates()[0].getU().type() == + write_ops::UpdateModification::Type::kClassic); + ASSERT_BSONOBJ_EQ(op.getUpdates()[0].getU().getUpdateClassic(), update); ASSERT_EQ(op.getUpdates()[0].getUpsert(), upsert); ASSERT_EQ(op.getUpdates()[0].getMulti(), multi); } diff --git a/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp b/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp index f0fae26f470..774e0ba802a 100644 --- a/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp +++ b/src/mongo/db/pipeline/document_source_graph_lookup_test.cpp @@ -100,7 +100,8 @@ TEST_F(DocumentSourceGraphLookUpTest, std::deque<DocumentSource::GetNextResult> fromContents{Document{{"to", 0}}}; NamespaceString fromNs("test", "graph_lookup"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto graphLookupStage = DocumentSourceGraphLookUp::create(expCtx, @@ -128,7 +129,8 @@ TEST_F(DocumentSourceGraphLookUpTest, Document{{"_id", "a"_sd}, {"to", 0}, {"from", 1}}, Document{{"to", 1}}}; NamespaceString fromNs("test", "graph_lookup"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto graphLookupStage = DocumentSourceGraphLookUp::create(expCtx, @@ -156,7 +158,8 @@ TEST_F(DocumentSourceGraphLookUpTest, std::deque<DocumentSource::GetNextResult> fromContents{Document{{"to", 0}}}; NamespaceString fromNs("test", "graph_lookup"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto unwindStage = DocumentSourceUnwind::create(expCtx, "results", false, boost::none); auto graphLookupStage = @@ -199,7 +202,8 @@ TEST_F(DocumentSourceGraphLookUpTest, Document(to1), Document(to2), Document(to0from1), Document(to0from2)}; NamespaceString fromNs("test", "graph_lookup"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto graphLookupStage = DocumentSourceGraphLookUp::create(expCtx, @@ -263,7 +267,8 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldPropagatePauses) { Document{{"_id", "a"_sd}, {"to", 0}, {"from", 1}}, Document{{"_id", "b"_sd}, {"to", 1}}}; NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto graphLookupStage = DocumentSourceGraphLookUp::create(expCtx, @@ -329,7 +334,10 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldPropagatePausesWhileUnwinding) { Document{{"_id", "a"_sd}, {"to", 0}, {"from", 1}}, Document{{"_id", "b"_sd}, {"to", 1}}}; NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); + expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); const bool preserveNullAndEmptyArrays = false; @@ -392,7 +400,8 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldPropagatePausesWhileUnwinding) { TEST_F(DocumentSourceGraphLookUpTest, GraphLookupShouldReportAsFieldIsModified) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::deque<DocumentSource::GetNextResult>{}); auto graphLookupStage = @@ -416,7 +425,8 @@ TEST_F(DocumentSourceGraphLookUpTest, GraphLookupShouldReportAsFieldIsModified) TEST_F(DocumentSourceGraphLookUpTest, GraphLookupShouldReportFieldsModifiedByAbsorbedUnwind) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::deque<DocumentSource::GetNextResult>{}); auto unwindStage = @@ -446,7 +456,8 @@ TEST_F(DocumentSourceGraphLookUpTest, GraphLookupWithComparisonExpressionForStar auto inputMock = DocumentSourceMock::create(Document({{"_id", 0}, {"a", 1}, {"b", 2}})); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); std::deque<DocumentSource::GetNextResult> fromContents{Document{{"_id", 0}, {"to", true}}, Document{{"_id", 1}, {"to", false}}}; expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); @@ -511,7 +522,8 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldExpandArraysAtEndOfConnectFromField) Document(sinkDoc)}; NamespaceString fromNs("test", "graph_lookup"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto graphLookupStage = DocumentSourceGraphLookUp::create(expCtx, @@ -583,7 +595,8 @@ TEST_F(DocumentSourceGraphLookUpTest, ShouldNotExpandArraysWithinArraysAtEndOfCo Document(startDoc), Document(target1), Document(target2), Document(soloDoc)}; NamespaceString fromNs("test", "graph_lookup"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->mongoProcessInterface = std::make_shared<MockMongoInterface>(std::move(fromContents)); auto graphLookupStage = DocumentSourceGraphLookUp::create(expCtx, diff --git a/src/mongo/db/pipeline/document_source_lookup_test.cpp b/src/mongo/db/pipeline/document_source_lookup_test.cpp index 5d18a271718..6c7d8660b62 100644 --- a/src/mongo/db/pipeline/document_source_lookup_test.cpp +++ b/src/mongo/db/pipeline/document_source_lookup_test.cpp @@ -86,7 +86,8 @@ public: TEST_F(DocumentSourceLookUpTest, PreservesParentPipelineLetVariables) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto varId = expCtx->variablesParseState.defineVariable("foo"); expCtx->variables.setValue(varId, Value(123)); @@ -110,7 +111,8 @@ TEST_F(DocumentSourceLookUpTest, PreservesParentPipelineLetVariables) { TEST_F(DocumentSourceLookUpTest, AcceptsPipelineSyntax) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( BSON("$lookup" << BSON("from" @@ -128,7 +130,8 @@ TEST_F(DocumentSourceLookUpTest, AcceptsPipelineSyntax) { TEST_F(DocumentSourceLookUpTest, AcceptsPipelineWithLetSyntax) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( BSON("$lookup" << BSON("from" @@ -151,7 +154,8 @@ TEST_F(DocumentSourceLookUpTest, AcceptsPipelineWithLetSyntax) { TEST_F(DocumentSourceLookUpTest, LookupEmptyPipelineDoesntUseDiskAndIsOKInATransaction) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( BSON("$lookup" << BSON("from" << fromNs.coll().toString() << "pipeline" << BSONArray() @@ -172,7 +176,8 @@ TEST_F(DocumentSourceLookUpTest, LookupWithOutInPipelineNotAllowed) { auto ERROR_CODE_OUT_BANNED_IN_LOOKUP = 51047; auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); ASSERT_THROWS_CODE( DocumentSourceLookUp::createFromBson(BSON("$lookup" << BSON("from" << "coll" @@ -219,7 +224,8 @@ TEST_F(DocumentSourceLookUpTest, LiteParsedDocumentSourceLookupContainsExpectedN TEST_F(DocumentSourceLookUpTest, RejectLookupWhenDepthLimitIsExceeded) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); expCtx->subPipelineDepth = DocumentSourceLookUp::kMaxSubPipelineDepth; @@ -239,7 +245,8 @@ TEST_F(DocumentSourceLookUpTest, RejectLookupWhenDepthLimitIsExceeded) { TEST_F(ReplDocumentSourceLookUpTest, RejectsPipelineWithChangeStreamStage) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Verify that attempting to create a $lookup pipeline containing a $changeStream stage fails. ASSERT_THROWS_CODE( @@ -254,7 +261,8 @@ TEST_F(ReplDocumentSourceLookUpTest, RejectsPipelineWithChangeStreamStage) { TEST_F(ReplDocumentSourceLookUpTest, RejectsSubPipelineWithChangeStreamStage) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Verify that attempting to create a sub-$lookup pipeline containing a $changeStream stage // fails at parse time, even if the outer pipeline does not have a $changeStream stage. @@ -271,7 +279,8 @@ TEST_F(ReplDocumentSourceLookUpTest, RejectsSubPipelineWithChangeStreamStage) { TEST_F(DocumentSourceLookUpTest, RejectsLocalFieldForeignFieldWhenPipelineIsSpecified) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); try { auto lookupStage = DocumentSourceLookUp::createFromBson( @@ -300,7 +309,8 @@ TEST_F(DocumentSourceLookUpTest, RejectsLocalFieldForeignFieldWhenPipelineIsSpec TEST_F(DocumentSourceLookUpTest, RejectsLocalFieldForeignFieldWhenLetIsSpecified) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); ASSERT_THROWS_CODE(DocumentSourceLookUp::createFromBson(BSON("$lookup" << BSON("from" << "coll" @@ -322,7 +332,8 @@ TEST_F(DocumentSourceLookUpTest, RejectsLocalFieldForeignFieldWhenLetIsSpecified TEST_F(DocumentSourceLookUpTest, RejectsInvalidLetVariableName) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); ASSERT_THROWS_CODE(DocumentSourceLookUp::createFromBson( BSON("$lookup" << BSON("from" @@ -373,7 +384,8 @@ TEST_F(DocumentSourceLookUpTest, RejectsInvalidLetVariableName) { TEST_F(DocumentSourceLookUpTest, ShouldBeAbleToReParseSerializedStage) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto lookupStage = DocumentSourceLookUp::createFromBson( BSON("$lookup" << BSON("from" @@ -534,7 +546,8 @@ private: TEST_F(DocumentSourceLookUpTest, ShouldPropagatePauses) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Set up the $lookup stage. auto lookupSpec = Document{{"$lookup", @@ -585,7 +598,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldPropagatePauses) { TEST_F(DocumentSourceLookUpTest, ShouldPropagatePausesWhileUnwinding) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Set up the $lookup stage. auto lookupSpec = Document{{"$lookup", @@ -638,7 +652,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldPropagatePausesWhileUnwinding) { TEST_F(DocumentSourceLookUpTest, LookupReportsAsFieldIsModified) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Set up the $lookup stage. auto lookupSpec = Document{{"$lookup", @@ -660,7 +675,8 @@ TEST_F(DocumentSourceLookUpTest, LookupReportsAsFieldIsModified) { TEST_F(DocumentSourceLookUpTest, LookupReportsFieldsModifiedByAbsorbedUnwind) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "foreign"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Set up the $lookup stage. auto lookupSpec = Document{{"$lookup", @@ -693,7 +709,8 @@ BSONObj sequentialCacheStageObj(const StringData status = "kBuilding"_sd, TEST_F(DocumentSourceLookUpTest, ShouldCacheNonCorrelatedSubPipelinePrefix) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( fromjson("{$lookup: {let: {var1: '$_id'}, pipeline: [{$match: {x:1}}, {$sort: {x: 1}}, " @@ -722,7 +739,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldDiscoverVariablesReferencedInFacetPipelineAfterAnExhaustiveAllStage) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // In the $facet stage here, the correlated $match stage comes after a $group stage which // returns EXHAUSTIVE_ALL for its dependencies. Verify that we continue enumerating the $facet @@ -757,7 +775,8 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ExprEmbeddedInMatchExpressionShouldBeOptimized) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // This pipeline includes a $match stage that itself includes a $expr expression. auto docSource = DocumentSourceLookUp::createFromBson( @@ -796,7 +815,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldIgnoreLocalVariablesShadowingLetVariablesWhenFindingNonCorrelatedPrefix) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // The $project stage defines a local variable with the same name as the $lookup 'let' variable. // Verify that the $project is identified as non-correlated and the cache is placed after it. @@ -830,7 +850,8 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldInsertCacheBeforeCorrelatedNestedLookup) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Create a $lookup stage whose pipeline contains nested $lookups. The third-level $lookup // refers to a 'let' variable defined in the top-level $lookup. Verify that the second-level @@ -867,7 +888,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldIgnoreNestedLookupLetVariablesShadowingOuterLookupLetVariablesWhenFindingPrefix) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // The nested $lookup stage defines a 'let' variable with the same name as the top-level 'let'. // Verify the nested $lookup is identified as non-correlated and the cache is placed after it. @@ -901,7 +923,8 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldCacheEntirePipelineIfNonCorrelated) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( fromjson("{$lookup: {let: {}, pipeline: [{$match: {x:1}}, {$sort: {x: 1}}, {$lookup: " @@ -934,7 +957,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldReplaceNonCorrelatedPrefixWithCacheAfterFirstSubPipelineIteration) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( fromjson( @@ -1008,7 +1032,8 @@ TEST_F(DocumentSourceLookUpTest, ShouldAbandonCacheIfMaxSizeIsExceededAfterFirstSubPipelineIteration) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); // Ensure the cache is abandoned after the first iteration by setting its max size to 0. size_t maxCacheSizeBytes = 0; @@ -1073,7 +1098,8 @@ TEST_F(DocumentSourceLookUpTest, TEST_F(DocumentSourceLookUpTest, ShouldNotCacheIfCorrelatedStageIsAbsorbedIntoPlanExecutor) { auto expCtx = getExpCtx(); NamespaceString fromNs("test", "coll"); - expCtx->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + expCtx->setResolvedNamespaces(StringMap<ExpressionContext::ResolvedNamespace>{ + {fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}}); auto docSource = DocumentSourceLookUp::createFromBson( fromjson("{$lookup: {let: {var1: '$_id'}, pipeline: [{$match: {$expr: { $gte: ['$x', " diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.h b/src/mongo/db/pipeline/document_source_single_document_transformation.h index 41003ef2141..d9f874fde64 100644 --- a/src/mongo/db/pipeline/document_source_single_document_transformation.h +++ b/src/mongo/db/pipeline/document_source_single_document_transformation.h @@ -65,6 +65,7 @@ public: ChangeStreamRequirement::kWhitelist); constraints.canSwapWithMatch = true; constraints.canSwapWithLimitAndSample = true; + constraints.isAllowedWithinUpdatePipeline = true; // This transformation could be part of a 'collectionless' change stream on an entire // database or cluster, mark as independent of any collection if so. constraints.isIndependentOfAnyCollection = _isIndependentOfAnyCollection; diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h index e3e9e76b2f3..c02310887c7 100644 --- a/src/mongo/db/pipeline/expression_context.h +++ b/src/mongo/db/pipeline/expression_context.h @@ -188,12 +188,8 @@ public: return tailableMode == TailableModeEnum::kTailableAndAwaitData; } - /** - * Sets the resolved definition for an involved namespace. - */ - void setResolvedNamespace_forTest(const NamespaceString& nss, - ResolvedNamespace resolvedNamespace) { - _resolvedNamespaces[nss.coll()] = std::move(resolvedNamespace); + void setResolvedNamespaces(StringMap<ResolvedNamespace> resolvedNamespaces) { + _resolvedNamespaces = std::move(resolvedNamespaces); } auto getRuntimeConstants() const { diff --git a/src/mongo/db/pipeline/lite_parsed_pipeline.h b/src/mongo/db/pipeline/lite_parsed_pipeline.h index 1d92064d584..4a45e171a4b 100644 --- a/src/mongo/db/pipeline/lite_parsed_pipeline.h +++ b/src/mongo/db/pipeline/lite_parsed_pipeline.h @@ -52,7 +52,7 @@ public: * May throw a AssertionException if there is an invalid stage specification, although full * validation happens later, during Pipeline construction. */ - LiteParsedPipeline(const AggregationRequest& request) : _nss(request.getNamespaceString()) { + LiteParsedPipeline(const AggregationRequest& request) { _stageSpecs.reserve(request.getPipeline().size()); for (auto&& rawStage : request.getPipeline()) { @@ -152,7 +152,6 @@ public: private: std::vector<std::unique_ptr<LiteParsedDocumentSource>> _stageSpecs; - NamespaceString _nss; }; } // namespace mongo diff --git a/src/mongo/db/pipeline/pipeline_metadata_tree_test.cpp b/src/mongo/db/pipeline/pipeline_metadata_tree_test.cpp index 128b820b753..2e0bf607a71 100644 --- a/src/mongo/db/pipeline/pipeline_metadata_tree_test.cpp +++ b/src/mongo/db/pipeline/pipeline_metadata_tree_test.cpp @@ -96,8 +96,12 @@ protected: void introduceCollection(StringData collectionName) { NamespaceString fromNs("test", collectionName); - getExpCtx()->setResolvedNamespace_forTest(fromNs, {fromNs, std::vector<BSONObj>{}}); + _resolvedNamespaces.insert({fromNs.coll().toString(), {fromNs, std::vector<BSONObj>()}}); + getExpCtx()->setResolvedNamespaces(_resolvedNamespaces); } + +private: + StringMap<ExpressionContext::ResolvedNamespace> _resolvedNamespaces; }; using namespace pipeline_metadata_tree; diff --git a/src/mongo/db/pipeline/stage_constraints.h b/src/mongo/db/pipeline/stage_constraints.h index 19961d7d45d..716258b7f68 100644 --- a/src/mongo/db/pipeline/stage_constraints.h +++ b/src/mongo/db/pipeline/stage_constraints.h @@ -289,5 +289,8 @@ struct StageConstraints { // order of documents can be swapped with a $sample because our implementation of sample will do // a random sort which shuffles the order. bool canSwapWithLimitAndSample = false; + + // Indicates that a stage is allowed within a pipeline-stlye update. + bool isAllowedWithinUpdatePipeline = false; }; } // namespace mongo diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 4f84da29db7..c957994bba9 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -1703,7 +1703,7 @@ Status applyOperation_inlock(OperationContext* opCtx, UpdateRequest request(requestNss); request.setQuery(b.done()); - request.setUpdates(o); + request.setUpdateModification(o); request.setUpsert(); request.setFromOplogApplication(true); @@ -1749,7 +1749,7 @@ Status applyOperation_inlock(OperationContext* opCtx, UpdateRequest request(requestNss); request.setQuery(updateCriteria); - request.setUpdates(o); + request.setUpdateModification(o); request.setUpsert(upsert); request.setFromOplogApplication(true); diff --git a/src/mongo/db/repl/rs_rollback.cpp b/src/mongo/db/repl/rs_rollback.cpp index 43b16bfc387..45a9a4a3458 100644 --- a/src/mongo/db/repl/rs_rollback.cpp +++ b/src/mongo/db/repl/rs_rollback.cpp @@ -1413,7 +1413,7 @@ void rollback_internal::syncFixUp(OperationContext* opCtx, UpdateRequest request(nss); request.setQuery(pattern); - request.setUpdates(idAndDoc.second); + request.setUpdateModification(idAndDoc.second); request.setGod(); request.setUpsert(); diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp index abb321c33c3..34128f17e80 100644 --- a/src/mongo/db/repl/storage_interface_impl.cpp +++ b/src/mongo/db/repl/storage_interface_impl.cpp @@ -907,7 +907,7 @@ Status StorageInterfaceImpl::upsertById(OperationContext* opCtx, // the event it was specified as a UUID. UpdateRequest request(collection->ns()); request.setQuery(query); - request.setUpdates(update); + request.setUpdateModification(update); request.setUpsert(true); invariant(!request.isMulti()); // This follows from using an exact _id query. invariant(!request.shouldReturnAnyDocs()); @@ -947,7 +947,7 @@ Status StorageInterfaceImpl::putSingleton(OperationContext* opCtx, const TimestampedBSONObj& update) { UpdateRequest request(nss); request.setQuery({}); - request.setUpdates(update.obj); + request.setUpdateModification(update.obj); request.setUpsert(true); return _updateWithQuery(opCtx, request, update.timestamp); } @@ -958,7 +958,7 @@ Status StorageInterfaceImpl::updateSingleton(OperationContext* opCtx, const TimestampedBSONObj& update) { UpdateRequest request(nss); request.setQuery(query); - request.setUpdates(update.obj); + request.setUpdateModification(update.obj); invariant(!request.isUpsert()); return _updateWithQuery(opCtx, request, update.timestamp); } diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp index 661edb58c25..fe673000166 100644 --- a/src/mongo/db/repl/storage_interface_impl_test.cpp +++ b/src/mongo/db/repl/storage_interface_impl_test.cpp @@ -2266,7 +2266,8 @@ TEST_F(StorageInterfaceImplTest, storage.upsertById(opCtx, nss, BSON("" << 1).firstElement(), unknownUpdateOp), AssertionException, ErrorCodes::FailedToParse, - "Unknown modifier: $unknownUpdateOp"); + "Unknown modifier: $unknownUpdateOp. Expected a valid update modifier or pipeline-style " + "update specified as an array"); ASSERT_THROWS_CODE(storage.upsertById(opCtx, {nss.db().toString(), *options.uuid}, diff --git a/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp index 2db9255e8a4..ce9460cf3e6 100644 --- a/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp +++ b/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp @@ -228,7 +228,8 @@ protected: ASSERT_EQ(itExpected->getUpsert(), itActual->getUpsert()); ASSERT_EQ(itExpected->getMulti(), itActual->getMulti()); ASSERT_BSONOBJ_EQ(itExpected->getQ(), itActual->getQ()); - ASSERT_BSONOBJ_EQ(itExpected->getU(), itActual->getU()); + ASSERT_BSONOBJ_EQ(itExpected->getU().getUpdateClassic(), + itActual->getU().getUpdateClassic()); } BatchedCommandResponse response; @@ -269,7 +270,8 @@ protected: ASSERT_EQ(itExpected->getUpsert(), itActual->getUpsert()); ASSERT_EQ(itExpected->getMulti(), itActual->getMulti()); ASSERT_BSONOBJ_EQ(itExpected->getQ(), itActual->getQ()); - ASSERT_BSONOBJ_EQ(itExpected->getU(), itActual->getU()); + ASSERT_BSONOBJ_EQ(itExpected->getU().getUpdateClassic(), + itActual->getU().getUpdateClassic()); } return statusToReturn; diff --git a/src/mongo/db/s/sharding_initialization_mongod.cpp b/src/mongo/db/s/sharding_initialization_mongod.cpp index c331d15c3aa..1902d884cee 100644 --- a/src/mongo/db/s/sharding_initialization_mongod.cpp +++ b/src/mongo/db/s/sharding_initialization_mongod.cpp @@ -313,7 +313,7 @@ Status ShardingInitializationMongoD::updateShardIdentityConfigString( UpdateRequest updateReq(NamespaceString::kServerConfigurationNamespace); updateReq.setQuery(BSON("_id" << ShardIdentityType::IdName)); - updateReq.setUpdates(updateObj); + updateReq.setUpdateModification(updateObj); try { AutoGetOrCreateDb autoDb( diff --git a/src/mongo/db/s/sharding_state_recovery.cpp b/src/mongo/db/s/sharding_state_recovery.cpp index 504db05d472..96fb297896c 100644 --- a/src/mongo/db/s/sharding_state_recovery.cpp +++ b/src/mongo/db/s/sharding_state_recovery.cpp @@ -170,7 +170,7 @@ Status modifyRecoveryDocument(OperationContext* opCtx, UpdateRequest updateReq(NamespaceString::kServerConfigurationNamespace); updateReq.setQuery(RecoveryDocument::getQuery()); - updateReq.setUpdates(updateObj); + updateReq.setUpdateModification(updateObj); updateReq.setUpsert(); UpdateResult result = update(opCtx, autoGetOrCreateDb->getDb(), updateReq); diff --git a/src/mongo/db/transaction_participant.cpp b/src/mongo/db/transaction_participant.cpp index 3c8c8353ca7..4405ba6b10e 100644 --- a/src/mongo/db/transaction_participant.cpp +++ b/src/mongo/db/transaction_participant.cpp @@ -204,7 +204,7 @@ ActiveTransactionHistory fetchActiveTransactionHistory(OperationContext* opCtx, void updateSessionEntry(OperationContext* opCtx, const UpdateRequest& updateRequest) { // Current code only supports replacement update. - dassert(UpdateDriver::isDocReplacement(updateRequest.getUpdates())); + dassert(UpdateDriver::isDocReplacement(updateRequest.getUpdateModification())); AutoGetCollection autoColl(opCtx, NamespaceString::kSessionTransactionsTableNamespace, MODE_IX); @@ -233,11 +233,11 @@ void updateSessionEntry(OperationContext* opCtx, const UpdateRequest& updateRequ dassert(idToFetch.fieldNameStringData() == "_id"_sd); auto recordId = indexAccess->findSingle(opCtx, toUpdateIdDoc); auto startingSnapshotId = opCtx->recoveryUnit()->getSnapshotId(); + const auto updateMod = updateRequest.getUpdateModification().getUpdateClassic(); if (recordId.isNull()) { // Upsert case. - auto status = collection->insertDocument( - opCtx, InsertStatement(updateRequest.getUpdates()), nullptr, false); + auto status = collection->insertDocument(opCtx, InsertStatement(updateMod), nullptr, false); if (status == ErrorCodes::DuplicateKey) { throw WriteConflictException(); @@ -262,14 +262,14 @@ void updateSessionEntry(OperationContext* opCtx, const UpdateRequest& updateRequ } CollectionUpdateArgs args; - args.update = updateRequest.getUpdates(); + args.update = updateMod; args.criteria = toUpdateIdDoc; args.fromMigrate = false; collection->updateDocument(opCtx, recordId, Snapshotted<BSONObj>(startingSnapshotId, originalDoc), - updateRequest.getUpdates(), + updateMod, false, // indexesAffected = false because _id is the only index nullptr, &args); @@ -2076,7 +2076,7 @@ UpdateRequest TransactionParticipant::Participant::_makeUpdateRequest( } return newTxnRecord.toBSON(); }(); - updateRequest.setUpdates(updateBSON); + updateRequest.setUpdateModification(updateBSON); updateRequest.setQuery(BSON(SessionTxnRecord::kSessionIdFieldName << _sessionId().toBSON())); updateRequest.setUpsert(true); diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index 39ca07b19b3..819a9c6e834 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -76,6 +76,7 @@ env.Library( 'modifier_node.cpp', 'modifier_table.cpp', 'object_replace_node.cpp', + 'pipeline_executor.cpp', 'pop_node.cpp', 'pull_node.cpp', 'pullall_node.cpp', @@ -91,6 +92,8 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/db/logical_clock', + '$BUILD_DIR/mongo/db/pipeline/document_source_mock', + '$BUILD_DIR/mongo/db/pipeline/pipeline', '$BUILD_DIR/mongo/db/update_index_data', 'update_common', ], @@ -105,6 +108,7 @@ env.CppUnitTest( 'compare_node_test.cpp', 'current_date_node_test.cpp', 'object_replace_node_test.cpp', + 'pipeline_executor_test.cpp', 'pop_node_test.cpp', 'pull_node_test.cpp', 'pullall_node_test.cpp', @@ -119,6 +123,7 @@ env.CppUnitTest( '$BUILD_DIR/mongo/bson/mutable/mutable_bson_test_utils', '$BUILD_DIR/mongo/db/service_context_test_fixture', '$BUILD_DIR/mongo/db/logical_clock', + '$BUILD_DIR/mongo/db/pipeline/document_value_test_util', '$BUILD_DIR/mongo/db/query/collation/collator_interface_mock', '$BUILD_DIR/mongo/db/query/query_test_service_context', 'update', @@ -141,8 +146,9 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/common', - '$BUILD_DIR/mongo/db/server_options_core', + '$BUILD_DIR/mongo/db/ops/write_ops_parsers', '$BUILD_DIR/mongo/db/query/query_planner', + '$BUILD_DIR/mongo/db/server_options_core', 'update', ], ) diff --git a/src/mongo/db/update/addtoset_node_test.cpp b/src/mongo/db/update/addtoset_node_test.cpp index 6263ca62071..9c3bfc283a5 100644 --- a/src/mongo/db/update/addtoset_node_test.cpp +++ b/src/mongo/db/update/addtoset_node_test.cpp @@ -113,7 +113,7 @@ TEST_F(AddToSetNodeTest, ApplyFailsOnNonArray) { mutablebson::Document doc(fromjson("{a: 2}")); setPathTaken("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"])), + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "Cannot apply $addToSet to non-array field. Field named 'a' has non-array type int"); @@ -128,7 +128,7 @@ TEST_F(AddToSetNodeTest, ApplyNonEach) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -146,7 +146,7 @@ TEST_F(AddToSetNodeTest, ApplyNonEachArray) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, [1]]}"), doc); @@ -164,7 +164,7 @@ TEST_F(AddToSetNodeTest, ApplyEach) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1, 2]}"), doc); @@ -182,7 +182,7 @@ TEST_F(AddToSetNodeTest, ApplyToEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc); @@ -200,7 +200,7 @@ TEST_F(AddToSetNodeTest, ApplyDeduplicateElementsToAdd) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -218,7 +218,7 @@ TEST_F(AddToSetNodeTest, ApplyDoNotAddExistingElements) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -236,7 +236,7 @@ TEST_F(AddToSetNodeTest, ApplyDoNotDeduplicateExistingElements) { mutablebson::Document doc(fromjson("{a: [0, 0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 0, 1]}"), doc); @@ -254,7 +254,7 @@ TEST_F(AddToSetNodeTest, ApplyNoElementsToAdd) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); @@ -272,7 +272,7 @@ TEST_F(AddToSetNodeTest, ApplyNoNonDuplicateElementsToAdd) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); @@ -290,7 +290,7 @@ TEST_F(AddToSetNodeTest, ApplyCreateArray) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -308,7 +308,7 @@ TEST_F(AddToSetNodeTest, ApplyCreateEmptyArrayIsNotNoop) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -328,7 +328,7 @@ TEST_F(AddToSetNodeTest, ApplyDeduplicationOfElementsToAddRespectsCollation) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['abc', 'def']}"), doc); @@ -348,7 +348,7 @@ TEST_F(AddToSetNodeTest, ApplyComparisonToExistingElementsRespectsCollation) { mutablebson::Document doc(fromjson("{a: ['ABC']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['ABC', 'def']}"), doc); @@ -370,7 +370,7 @@ TEST_F(AddToSetNodeTest, ApplyRespectsCollationFromSetCollator) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['abc', 'def']}"), doc); @@ -410,7 +410,7 @@ TEST_F(AddToSetNodeTest, ApplyNestedArray) { mutablebson::Document doc(fromjson("{ _id : 1, a : [ 1, [ ] ] }")); setPathTaken("a.1"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][1])); + auto result = node.apply(getApplyParams(doc.root()["a"][1]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{ _id : 1, a : [ 1, [ 1 ] ] }"), doc); @@ -428,7 +428,7 @@ TEST_F(AddToSetNodeTest, ApplyIndexesNotAffected) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_FALSE(doc.isInPlaceModeEnabled()); @@ -445,7 +445,7 @@ TEST_F(AddToSetNodeTest, ApplyNoIndexDataOrLogBuilder) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); diff --git a/src/mongo/db/update/arithmetic_node_test.cpp b/src/mongo/db/update/arithmetic_node_test.cpp index 1ce58fb77a7..d18cc4f1314 100644 --- a/src/mongo/db/update/arithmetic_node_test.cpp +++ b/src/mongo/db/update/arithmetic_node_test.cpp @@ -120,7 +120,7 @@ TEST_F(ArithmeticNodeTest, ApplyIncNoOp) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); @@ -138,7 +138,7 @@ TEST_F(ArithmeticNodeTest, ApplyMulNoOp) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); @@ -156,7 +156,7 @@ TEST_F(ArithmeticNodeTest, ApplyRoundingNoOp) { mutablebson::Document doc(fromjson("{a: 6.022e23}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6.022e23}"), doc); @@ -174,7 +174,7 @@ TEST_F(ArithmeticNodeTest, ApplyEmptyPathToCreate) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 11}"), doc); @@ -193,7 +193,7 @@ TEST_F(ArithmeticNodeTest, ApplyCreatePath) { setPathToCreate("b.c"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {d: 5, b: {c: 6}}}"), doc); @@ -212,7 +212,7 @@ TEST_F(ArithmeticNodeTest, ApplyExtendPath) { setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc); @@ -229,7 +229,7 @@ TEST_F(ArithmeticNodeTest, ApplyCreatePathFromRoot) { mutablebson::Document doc(fromjson("{c: 5}")); setPathToCreate("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{c: 5, a: {b: 6}}"), doc); @@ -248,7 +248,7 @@ TEST_F(ArithmeticNodeTest, ApplyPositional) { setPathTaken("a.1"); setMatchedField("1"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][1])); + auto result = node.apply(getApplyParams(doc.root()["a"][1]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 7, 2]}"), doc); @@ -267,10 +267,11 @@ TEST_F(ArithmeticNodeTest, ApplyNonViablePathToInc) { setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field 'b' in element {a: 5}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field 'b' in element {a: 5}"); } TEST_F(ArithmeticNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { @@ -284,7 +285,7 @@ TEST_F(ArithmeticNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { setPathTaken("a"); addIndexedPath("a"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); @@ -302,7 +303,7 @@ TEST_F(ArithmeticNodeTest, ApplyNoIndexDataNoLogBuilder) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 11}"), doc); @@ -319,7 +320,7 @@ TEST_F(ArithmeticNodeTest, ApplyDoesNotAffectIndexes) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 11}"), doc); @@ -336,7 +337,7 @@ TEST_F(ArithmeticNodeTest, IncTypePromotionIsNotANoOp) { mutablebson::Document doc(fromjson("{a: NumberInt(2)}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc); @@ -353,7 +354,7 @@ TEST_F(ArithmeticNodeTest, MulTypePromotionIsNotANoOp) { mutablebson::Document doc(fromjson("{a: NumberInt(2)}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc); @@ -370,7 +371,7 @@ TEST_F(ArithmeticNodeTest, TypePromotionFromIntToDecimalIsNotANoOp) { mutablebson::Document doc(fromjson("{a: NumberInt(5)}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"5.0\")}"), doc); @@ -388,7 +389,7 @@ TEST_F(ArithmeticNodeTest, TypePromotionFromLongToDecimalIsNotANoOp) { mutablebson::Document doc(fromjson("{a: NumberLong(5)}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"5.0\")}"), doc); @@ -406,7 +407,7 @@ TEST_F(ArithmeticNodeTest, TypePromotionFromDoubleToDecimalIsNotANoOp) { mutablebson::Document doc(fromjson("{a: 5.25}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"5.25\")}"), doc); @@ -424,7 +425,7 @@ TEST_F(ArithmeticNodeTest, ApplyPromoteToFloatingPoint) { mutablebson::Document doc(fromjson("{a: NumberLong(1)}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1.2}"), doc); @@ -441,7 +442,7 @@ TEST_F(ArithmeticNodeTest, IncrementedDecimalStaysDecimal) { mutablebson::Document doc(fromjson("{a: NumberDecimal(\"6.25\")}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberDecimal(\"11.5\")}"), doc); @@ -461,7 +462,7 @@ TEST_F(ArithmeticNodeTest, OverflowIntToLong) { ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType()); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType()); @@ -481,7 +482,7 @@ TEST_F(ArithmeticNodeTest, UnderflowIntToLong) { ASSERT_EQUALS(mongo::NumberInt, doc.root()["a"].getType()); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(mongo::NumberLong, doc.root()["a"].getType()); @@ -500,7 +501,7 @@ TEST_F(ArithmeticNodeTest, IncModeCanBeReused) { mutablebson::Document doc2(fromjson("{a: 2}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc1.root()["a"])); + auto result = node.apply(getApplyParams(doc1.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc1); @@ -510,7 +511,7 @@ TEST_F(ArithmeticNodeTest, IncModeCanBeReused) { resetApplyParams(); setPathTaken("a"); addIndexedPath("a"); - result = node.apply(getApplyParams(doc2.root()["a"])); + result = node.apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 3}"), doc2); @@ -527,7 +528,7 @@ TEST_F(ArithmeticNodeTest, CreatedNumberHasSameTypeAsInc) { mutablebson::Document doc(fromjson("{b: 6}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 6, a: NumberLong(5)}"), doc); @@ -544,7 +545,7 @@ TEST_F(ArithmeticNodeTest, CreatedNumberHasSameTypeAsMul) { mutablebson::Document doc(fromjson("{b: 6}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 6, a: NumberLong(0)}"), doc); @@ -561,7 +562,7 @@ TEST_F(ArithmeticNodeTest, ApplyEmptyDocument) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -578,11 +579,12 @@ TEST_F(ArithmeticNodeTest, ApplyIncToObjectFails) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: {b: 1}}")); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::TypeMismatch, - "Cannot apply $inc to a value of non-numeric type. {_id: " - "\"test_object\"} has the field 'a' of non-numeric type object"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::TypeMismatch, + "Cannot apply $inc to a value of non-numeric type. {_id: " + "\"test_object\"} has the field 'a' of non-numeric type object"); } TEST_F(ArithmeticNodeTest, ApplyIncToArrayFails) { @@ -594,11 +596,12 @@ TEST_F(ArithmeticNodeTest, ApplyIncToArrayFails) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: []}")); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::TypeMismatch, - "Cannot apply $inc to a value of non-numeric type. {_id: " - "\"test_object\"} has the field 'a' of non-numeric type array"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::TypeMismatch, + "Cannot apply $inc to a value of non-numeric type. {_id: " + "\"test_object\"} has the field 'a' of non-numeric type array"); } TEST_F(ArithmeticNodeTest, ApplyIncToStringFails) { @@ -610,11 +613,12 @@ TEST_F(ArithmeticNodeTest, ApplyIncToStringFails) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: \"foo\"}")); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::TypeMismatch, - "Cannot apply $inc to a value of non-numeric type. {_id: " - "\"test_object\"} has the field 'a' of non-numeric type string"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::TypeMismatch, + "Cannot apply $inc to a value of non-numeric type. {_id: " + "\"test_object\"} has the field 'a' of non-numeric type string"); } TEST_F(ArithmeticNodeTest, OverflowingOperationFails) { @@ -626,12 +630,13 @@ TEST_F(ArithmeticNodeTest, OverflowingOperationFails) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: NumberLong(9223372036854775807)}")); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::BadValue, - "Failed to apply $mul operations to current value " - "((NumberLong)9223372036854775807) for document {_id: " - "\"test_object\"}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "Failed to apply $mul operations to current value " + "((NumberLong)9223372036854775807) for document {_id: " + "\"test_object\"}"); } TEST_F(ArithmeticNodeTest, ApplyNewPath) { @@ -643,7 +648,7 @@ TEST_F(ArithmeticNodeTest, ApplyNewPath) { mutablebson::Document doc(fromjson("{b: 1}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc); @@ -659,7 +664,7 @@ TEST_F(ArithmeticNodeTest, ApplyEmptyIndexData) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: 3}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -676,7 +681,7 @@ TEST_F(ArithmeticNodeTest, ApplyNoOpDottedPath) { mutablebson::Document doc(fromjson("{a: {b: 2}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : 2}}"), doc); @@ -693,7 +698,7 @@ TEST_F(ArithmeticNodeTest, TypePromotionOnDottedPathIsNotANoOp) { mutablebson::Document doc(fromjson("{a: {b: NumberInt(2)}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : NumberLong(2)}}"), doc); @@ -710,10 +715,11 @@ TEST_F(ArithmeticNodeTest, ApplyPathNotViableArray) { mutablebson::Document doc(fromjson("{a:[{b:1}]}")); setPathToCreate("b"); setPathTaken("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field 'b' in element {a: [ { b: 1 } ]}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field 'b' in element {a: [ { b: 1 } ]}"); } TEST_F(ArithmeticNodeTest, ApplyInPlaceDottedPath) { @@ -725,7 +731,7 @@ TEST_F(ArithmeticNodeTest, ApplyInPlaceDottedPath) { mutablebson::Document doc(fromjson("{a: {b: 1}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc); @@ -742,7 +748,7 @@ TEST_F(ArithmeticNodeTest, ApplyPromotionDottedPath) { mutablebson::Document doc(fromjson("{a: {b: NumberInt(3)}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: NumberLong(5)}}"), doc); @@ -759,7 +765,7 @@ TEST_F(ArithmeticNodeTest, ApplyDottedPathEmptyDoc) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -776,7 +782,7 @@ TEST_F(ArithmeticNodeTest, ApplyFieldWithDot) { mutablebson::Document doc(fromjson("{'a.b':4}")); setPathToCreate("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc); @@ -793,7 +799,7 @@ TEST_F(ArithmeticNodeTest, ApplyNoOpArrayIndex) { mutablebson::Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}")); setPathTaken("a.2.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); @@ -811,7 +817,7 @@ TEST_F(ArithmeticNodeTest, TypePromotionInArrayIsNotANoOp) { fromjson("{a: [{b: NumberInt(0)},{b: NumberInt(1)},{b: NumberInt(2)}]}")); setPathTaken("a.2.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: NumberLong(2)}]}"), doc); @@ -828,10 +834,11 @@ TEST_F(ArithmeticNodeTest, ApplyNonViablePathThroughArray) { mutablebson::Document doc(fromjson("{a: 0}")); setPathToCreate("2.b"); setPathTaken("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field '2' in element {a: 0}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field '2' in element {a: 0}"); } TEST_F(ArithmeticNodeTest, ApplyInPlaceArrayIndex) { @@ -843,7 +850,7 @@ TEST_F(ArithmeticNodeTest, ApplyInPlaceArrayIndex) { mutablebson::Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}")); setPathTaken("a.2.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 3}]}"), doc); @@ -861,7 +868,7 @@ TEST_F(ArithmeticNodeTest, ApplyAppendArray) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); @@ -879,7 +886,7 @@ TEST_F(ArithmeticNodeTest, ApplyPaddingArray) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc); @@ -897,7 +904,7 @@ TEST_F(ArithmeticNodeTest, ApplyNumericObject) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc); @@ -914,7 +921,7 @@ TEST_F(ArithmeticNodeTest, ApplyNumericField) { mutablebson::Document doc(fromjson("{a: {'2': {b: 1}}}")); setPathTaken("a.2.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 3}}}"), doc); @@ -932,7 +939,7 @@ TEST_F(ArithmeticNodeTest, ApplyExtendNumericField) { setPathToCreate("b"); setPathTaken("a.2"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["2"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc); @@ -950,7 +957,7 @@ TEST_F(ArithmeticNodeTest, ApplyNumericFieldToEmptyObject) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); @@ -968,7 +975,7 @@ TEST_F(ArithmeticNodeTest, ApplyEmptyArray) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc); @@ -985,7 +992,7 @@ TEST_F(ArithmeticNodeTest, ApplyLogDottedPath) { mutablebson::Document doc(fromjson("{a: [{b:0}, {b:1}]}")); setPathToCreate("2.b"); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: [{b:0}, {b:1}, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -1002,7 +1009,7 @@ TEST_F(ArithmeticNodeTest, LogEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathToCreate("2.b"); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: [null, null, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -1019,7 +1026,7 @@ TEST_F(ArithmeticNodeTest, LogEmptyObject) { mutablebson::Document doc(fromjson("{a: {}}")); setPathToCreate("2.b"); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -1038,7 +1045,7 @@ TEST_F(ArithmeticNodeTest, ApplyDeserializedDocNotNoOp) { doc.root()["a"].setValueInt(1).transitional_ignore(); setPathToCreate("b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: NumberInt(0)}"), doc); @@ -1058,7 +1065,7 @@ TEST_F(ArithmeticNodeTest, ApplyToDeserializedDocNoOp) { doc.root()["a"].setValueInt(2).transitional_ignore(); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberInt(2)}"), doc); @@ -1078,7 +1085,7 @@ TEST_F(ArithmeticNodeTest, ApplyToDeserializedDocNestedNoop) { doc.root().appendObject("a", BSON("b" << static_cast<int>(1))).transitional_ignore(); setPathTaken("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: NumberInt(1)}}"), doc); @@ -1098,7 +1105,7 @@ TEST_F(ArithmeticNodeTest, ApplyToDeserializedDocNestedNotNoop) { doc.root().appendObject("a", BSON("b" << static_cast<int>(1))).transitional_ignore(); setPathTaken("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc); diff --git a/src/mongo/db/update/bit_node_test.cpp b/src/mongo/db/update/bit_node_test.cpp index afb1f4fabb5..78734dd63dd 100644 --- a/src/mongo/db/update/bit_node_test.cpp +++ b/src/mongo/db/update/bit_node_test.cpp @@ -159,7 +159,7 @@ TEST_F(BitNodeTest, ApplyAndLogEmptyDocumentAnd) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 0}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); @@ -174,7 +174,7 @@ TEST_F(BitNodeTest, ApplyAndLogEmptyDocumentOr) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); @@ -189,7 +189,7 @@ TEST_F(BitNodeTest, ApplyAndLogEmptyDocumentXor) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 1}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); @@ -204,7 +204,7 @@ TEST_F(BitNodeTest, ApplyAndLogSimpleDocumentAnd) { mutablebson::Document doc(BSON("a" << 0b0101)); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0100), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); @@ -219,7 +219,7 @@ TEST_F(BitNodeTest, ApplyAndLogSimpleDocumentOr) { mutablebson::Document doc(BSON("a" << 0b0101)); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0111), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); @@ -234,7 +234,7 @@ TEST_F(BitNodeTest, ApplyAndLogSimpleDocumentXor) { mutablebson::Document doc(BSON("a" << 0b0101)); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0011), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); @@ -249,7 +249,7 @@ TEST_F(BitNodeTest, ApplyShouldReportNoOp) { mutablebson::Document doc(BSON("a" << 1)); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_EQUALS(BSON("a" << static_cast<int>(1)), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); @@ -269,7 +269,7 @@ TEST_F(BitNodeTest, ApplyMultipleBitOps) { mutablebson::Document doc(BSON("a" << 0b1111111100000000)); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b0101011001100110), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); @@ -284,7 +284,7 @@ TEST_F(BitNodeTest, ApplyRepeatedBitOps) { mutablebson::Document doc(BSON("a" << 0b11110000)); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(BSON("a" << 0b10010110), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); diff --git a/src/mongo/db/update/compare_node_test.cpp b/src/mongo/db/update/compare_node_test.cpp index e6fcc1005fb..b500701cf2d 100644 --- a/src/mongo/db/update/compare_node_test.cpp +++ b/src/mongo/db/update/compare_node_test.cpp @@ -63,7 +63,7 @@ TEST_F(CompareNodeTest, ApplyMaxSameNumber) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); @@ -80,7 +80,7 @@ TEST_F(CompareNodeTest, ApplyMinSameNumber) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); @@ -97,7 +97,7 @@ TEST_F(CompareNodeTest, ApplyMaxNumberIsLess) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); @@ -114,7 +114,7 @@ TEST_F(CompareNodeTest, ApplyMinNumberIsMore) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); @@ -131,7 +131,7 @@ TEST_F(CompareNodeTest, ApplyMaxSameValInt) { mutablebson::Document doc(fromjson("{a: 1.0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1.0}"), doc); @@ -148,7 +148,7 @@ TEST_F(CompareNodeTest, ApplyMaxSameValIntZero) { mutablebson::Document doc(fromjson("{a: 0.0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0.0}"), doc); @@ -165,7 +165,7 @@ TEST_F(CompareNodeTest, ApplyMinSameValIntZero) { mutablebson::Document doc(fromjson("{a: 0.0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0.0}"), doc); @@ -182,7 +182,7 @@ TEST_F(CompareNodeTest, ApplyMissingFieldMinNumber) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0}"), doc); @@ -199,7 +199,7 @@ TEST_F(CompareNodeTest, ApplyExistingNumberMinNumber) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0}"), doc); @@ -216,7 +216,7 @@ TEST_F(CompareNodeTest, ApplyMissingFieldMaxNumber) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 0}"), doc); @@ -233,7 +233,7 @@ TEST_F(CompareNodeTest, ApplyExistingNumberMaxNumber) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -250,7 +250,7 @@ TEST_F(CompareNodeTest, ApplyExistingDateMaxDate) { mutablebson::Document doc(fromjson("{a: {$date: 0}}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {$date: 123123123}}"), doc); @@ -267,7 +267,7 @@ TEST_F(CompareNodeTest, ApplyExistingEmbeddedDocMaxDoc) { mutablebson::Document doc(fromjson("{a: {b: 2}}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 3}}"), doc); @@ -284,7 +284,7 @@ TEST_F(CompareNodeTest, ApplyExistingEmbeddedDocMaxNumber) { mutablebson::Document doc(fromjson("{a: {b: 2}}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -303,7 +303,7 @@ TEST_F(CompareNodeTest, ApplyMinRespectsCollation) { mutablebson::Document doc(fromjson("{a: 'cbc'}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 'dba'}"), doc); @@ -324,7 +324,7 @@ TEST_F(CompareNodeTest, ApplyMinRespectsCollationFromSetCollator) { mutablebson::Document doc(fromjson("{a: 'cbc'}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 'dba'}"), doc); @@ -345,7 +345,7 @@ TEST_F(CompareNodeTest, ApplyMaxRespectsCollationFromSetCollator) { mutablebson::Document doc(fromjson("{a: 'cbc'}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 'abd'}"), doc); @@ -384,7 +384,7 @@ TEST_F(CompareNodeTest, ApplyIndexesNotAffected) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); addIndexedPath("b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); @@ -401,7 +401,7 @@ TEST_F(CompareNodeTest, ApplyNoIndexDataOrLogBuilder) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); diff --git a/src/mongo/db/update/conflict_placeholder_node.h b/src/mongo/db/update/conflict_placeholder_node.h index 66d29e854c8..469613a515c 100644 --- a/src/mongo/db/update/conflict_placeholder_node.h +++ b/src/mongo/db/update/conflict_placeholder_node.h @@ -60,7 +60,8 @@ public: void setCollator(const CollatorInterface* collator) final {} - ApplyResult apply(ApplyParams applyParams) const final { + ApplyResult apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const final { return ApplyResult::noopResult(); } diff --git a/src/mongo/db/update/current_date_node_test.cpp b/src/mongo/db/update/current_date_node_test.cpp index 4fa7a45c83a..7bd11c9140b 100644 --- a/src/mongo/db/update/current_date_node_test.cpp +++ b/src/mongo/db/update/current_date_node_test.cpp @@ -132,7 +132,7 @@ TEST_F(CurrentDateNodeTest, ApplyTrue) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); @@ -156,7 +156,7 @@ TEST_F(CurrentDateNodeTest, ApplyFalse) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); @@ -180,7 +180,7 @@ TEST_F(CurrentDateNodeTest, ApplyDate) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); @@ -204,7 +204,7 @@ TEST_F(CurrentDateNodeTest, ApplyTimestamp) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); @@ -228,7 +228,7 @@ TEST_F(CurrentDateNodeTest, ApplyFieldDoesNotExist) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); @@ -252,7 +252,7 @@ TEST_F(CurrentDateNodeTest, ApplyIndexesNotAffected) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); addIndexedPath("b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); @@ -276,7 +276,7 @@ TEST_F(CurrentDateNodeTest, ApplyNoIndexDataOrLogBuilder) { mutablebson::Document doc(fromjson("{a: 0}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); diff --git a/src/mongo/db/update/modifier_node.cpp b/src/mongo/db/update/modifier_node.cpp index 654118b5361..674a2d8e361 100644 --- a/src/mongo/db/update/modifier_node.cpp +++ b/src/mongo/db/update/modifier_node.cpp @@ -146,9 +146,10 @@ void checkImmutablePathsNotModified(mutablebson::Element element, } // namespace -UpdateNode::ApplyResult ModifierNode::applyToExistingElement(ApplyParams applyParams) const { - invariant(!applyParams.pathTaken->empty()); - invariant(applyParams.pathToCreate->empty()); +UpdateExecutor::ApplyResult ModifierNode::applyToExistingElement( + ApplyParams applyParams, UpdateNodeApplyParams updateNodeApplyParams) const { + invariant(!updateNodeApplyParams.pathTaken->empty()); + invariant(updateNodeApplyParams.pathToCreate->empty()); invariant(applyParams.element.ok()); mutablebson::ConstElement leftSibling = applyParams.element.leftSibling(); @@ -159,7 +160,7 @@ UpdateNode::ApplyResult ModifierNode::applyToExistingElement(ApplyParams applyPa for (auto immutablePath = applyParams.immutablePaths.begin(); immutablePath != applyParams.immutablePaths.end(); ++immutablePath) { - if (applyParams.pathTaken->isPrefixOf(**immutablePath)) { + if (updateNodeApplyParams.pathTaken->isPrefixOf(**immutablePath)) { compareWithOriginal = true; break; } @@ -172,37 +173,40 @@ UpdateNode::ApplyResult ModifierNode::applyToExistingElement(ApplyParams applyPa ModifyResult updateResult; if (compareWithOriginal) { BSONObj original = applyParams.element.getDocument().getObject(); - updateResult = updateExistingElement(&applyParams.element, applyParams.pathTaken); + updateResult = updateExistingElement(&applyParams.element, updateNodeApplyParams.pathTaken); if (updateResult == ModifyResult::kNoOp) { return ApplyResult::noopResult(); } - checkImmutablePathsNotModifiedFromOriginal( - applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths, original); + checkImmutablePathsNotModifiedFromOriginal(applyParams.element, + updateNodeApplyParams.pathTaken.get(), + applyParams.immutablePaths, + original); } else { - updateResult = updateExistingElement(&applyParams.element, applyParams.pathTaken); + updateResult = updateExistingElement(&applyParams.element, updateNodeApplyParams.pathTaken); if (updateResult == ModifyResult::kNoOp) { return ApplyResult::noopResult(); } checkImmutablePathsNotModified( - applyParams.element, applyParams.pathTaken.get(), applyParams.immutablePaths); + applyParams.element, updateNodeApplyParams.pathTaken.get(), applyParams.immutablePaths); } invariant(updateResult != ModifyResult::kCreated); ApplyResult applyResult; - if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(*applyParams.pathTaken)) { + if (!applyParams.indexData || + !applyParams.indexData->mightBeIndexed(*updateNodeApplyParams.pathTaken)) { applyResult.indexesAffected = false; } if (applyParams.validateForStorage) { - const uint32_t recursionLevel = applyParams.pathTaken->numParts(); + const uint32_t recursionLevel = updateNodeApplyParams.pathTaken->numParts(); validateUpdate( applyParams.element, leftSibling, rightSibling, recursionLevel, updateResult); } if (applyParams.logBuilder) { logUpdate(applyParams.logBuilder, - applyParams.pathTaken->dottedField(), + updateNodeApplyParams.pathTaken->dottedField(), applyParams.element, updateResult); } @@ -210,16 +214,17 @@ UpdateNode::ApplyResult ModifierNode::applyToExistingElement(ApplyParams applyPa return applyResult; } -UpdateNode::ApplyResult ModifierNode::applyToNonexistentElement(ApplyParams applyParams) const { +UpdateExecutor::ApplyResult ModifierNode::applyToNonexistentElement( + ApplyParams applyParams, UpdateNodeApplyParams updateNodeApplyParams) const { if (allowCreation()) { - auto newElementFieldName = - applyParams.pathToCreate->getPart(applyParams.pathToCreate->numParts() - 1); + auto newElementFieldName = updateNodeApplyParams.pathToCreate->getPart( + updateNodeApplyParams.pathToCreate->numParts() - 1); auto newElement = applyParams.element.getDocument().makeElementNull(newElementFieldName); setValueForNewElement(&newElement); invariant(newElement.ok()); auto statusWithFirstCreatedElem = pathsupport::createPathAt( - *(applyParams.pathToCreate), 0, applyParams.element, newElement); + *(updateNodeApplyParams.pathToCreate), 0, applyParams.element, newElement); if (!statusWithFirstCreatedElem.isOK()) { // $set operaions on non-viable paths are ignored when the update came from replication. // We do not error because idempotency requires that any other update modifiers must @@ -241,7 +246,7 @@ UpdateNode::ApplyResult ModifierNode::applyToNonexistentElement(ApplyParams appl } if (applyParams.validateForStorage) { - const uint32_t recursionLevel = applyParams.pathTaken->numParts() + 1; + const uint32_t recursionLevel = updateNodeApplyParams.pathTaken->numParts() + 1; mutablebson::ConstElement elementForValidation = statusWithFirstCreatedElem.getValue(); validateUpdate(elementForValidation, elementForValidation.leftSibling(), @@ -259,27 +264,29 @@ UpdateNode::ApplyResult ModifierNode::applyToNonexistentElement(ApplyParams appl // (Note that this behavior is subtly different from checkImmutablePathsNotModified(), // because we just created this element.) uassert(ErrorCodes::ImmutableField, - str::stream() << "Updating the path '" << applyParams.pathTaken->dottedField() + str::stream() << "Updating the path '" + << updateNodeApplyParams.pathTaken->dottedField() << "' to " << applyParams.element.toString() << " would modify the immutable field '" << (*immutablePath)->dottedField() << "'", - applyParams.pathTaken->commonPrefixSize(**immutablePath) != + updateNodeApplyParams.pathTaken->commonPrefixSize(**immutablePath) != (*immutablePath)->numParts()); } - invariant(!applyParams.pathToCreate->empty()); + invariant(!updateNodeApplyParams.pathToCreate->empty()); FieldRef fullPath; - if (applyParams.pathTaken->empty()) { - fullPath = *applyParams.pathToCreate; + if (updateNodeApplyParams.pathTaken->empty()) { + fullPath = *updateNodeApplyParams.pathToCreate; } else { - fullPath = FieldRef(str::stream() << applyParams.pathTaken->dottedField() << "." - << applyParams.pathToCreate->dottedField()); + fullPath = + FieldRef(str::stream() << updateNodeApplyParams.pathTaken->dottedField() << "." + << updateNodeApplyParams.pathToCreate->dottedField()); // If adding an element to an array, only mark the path to the array itself as modified. if (applyParams.modifiedPaths && applyParams.element.getType() == BSONType::Array) { - applyParams.modifiedPaths->keepShortest(*applyParams.pathTaken); + applyParams.modifiedPaths->keepShortest(*updateNodeApplyParams.pathTaken); } } @@ -294,7 +301,7 @@ UpdateNode::ApplyResult ModifierNode::applyToNonexistentElement(ApplyParams appl if (!applyParams.indexData || !applyParams.indexData->mightBeIndexed(applyParams.element.getType() != BSONType::Array ? fullPath - : *applyParams.pathTaken)) { + : *updateNodeApplyParams.pathTaken)) { applyResult.indexesAffected = false; } @@ -310,26 +317,29 @@ UpdateNode::ApplyResult ModifierNode::applyToNonexistentElement(ApplyParams appl if (!allowNonViablePath()) { // One exception: some of these modifiers still fail when the nonexistent path is // "non-viable," meaning it couldn't be created even if we intended to. - UpdateLeafNode::checkViability( - applyParams.element, *(applyParams.pathToCreate), *(applyParams.pathTaken)); + UpdateLeafNode::checkViability(applyParams.element, + *(updateNodeApplyParams.pathToCreate), + *(updateNodeApplyParams.pathTaken)); } return ApplyResult::noopResult(); } } -UpdateNode::ApplyResult ModifierNode::apply(ApplyParams applyParams) const { +UpdateExecutor::ApplyResult ModifierNode::apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const { ApplyResult result; if (context == Context::kInsertOnly && !applyParams.insert) { result = ApplyResult::noopResult(); - } else if (!applyParams.pathToCreate->empty()) { - result = applyToNonexistentElement(applyParams); + } else if (!updateNodeApplyParams.pathToCreate->empty()) { + result = applyToNonexistentElement(applyParams, updateNodeApplyParams); } else { - result = applyToExistingElement(applyParams); + result = applyToExistingElement(applyParams, updateNodeApplyParams); } if (applyParams.modifiedPaths) { - applyParams.modifiedPaths->keepShortest(*applyParams.pathTaken + *applyParams.pathToCreate); + applyParams.modifiedPaths->keepShortest(*updateNodeApplyParams.pathTaken + + *updateNodeApplyParams.pathToCreate); } return result; diff --git a/src/mongo/db/update/modifier_node.h b/src/mongo/db/update/modifier_node.h index 3d48933175e..4649cbc03aa 100644 --- a/src/mongo/db/update/modifier_node.h +++ b/src/mongo/db/update/modifier_node.h @@ -57,7 +57,8 @@ class ModifierNode : public UpdateLeafNode { public: explicit ModifierNode(Context context = Context::kAll) : UpdateLeafNode(context) {} - ApplyResult apply(ApplyParams applyParams) const final; + ApplyResult apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const final; protected: enum class ModifyResult { @@ -195,8 +196,10 @@ private: */ virtual BSONObj operatorValue() const = 0; - ApplyResult applyToNonexistentElement(ApplyParams applyParams) const; - ApplyResult applyToExistingElement(ApplyParams applyParams) const; + ApplyResult applyToNonexistentElement(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const; + ApplyResult applyToExistingElement(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const; }; } // namespace mongo diff --git a/src/mongo/db/update/object_replace_node.cpp b/src/mongo/db/update/object_replace_node.cpp index 88d836d0303..42f89906181 100644 --- a/src/mongo/db/update/object_replace_node.cpp +++ b/src/mongo/db/update/object_replace_node.cpp @@ -44,8 +44,7 @@ namespace { constexpr StringData kIdFieldName = "_id"_sd; } // namespace -ObjectReplaceNode::ObjectReplaceNode(BSONObj value) - : UpdateNode(Type::Replacement), val(value.getOwned()), _containsId(false) { +ObjectReplaceNode::ObjectReplaceNode(BSONObj value) : val(value.getOwned()), _containsId(false) { // Replace all zero-valued timestamps with the current time and check for the existence of _id. for (auto&& elem : val) { @@ -70,14 +69,12 @@ ObjectReplaceNode::ObjectReplaceNode(BSONObj value) } } -UpdateNode::ApplyResult ObjectReplaceNode::apply(ApplyParams applyParams) const { - invariant(applyParams.pathToCreate->empty()); - invariant(applyParams.pathTaken->empty()); - - auto original = applyParams.element.getDocument().getObject(); +UpdateExecutor::ApplyResult ObjectReplaceNode::applyReplacementUpdate( + ApplyParams applyParams, const BSONObj& replacementDoc, bool replacementDocContainsIdField) { + auto originalDoc = applyParams.element.getDocument().getObject(); // Check for noop. - if (original.binaryEqual(val)) { + if (originalDoc.binaryEqual(replacementDoc)) { return ApplyResult::noopResult(); } @@ -86,7 +83,7 @@ UpdateNode::ApplyResult ObjectReplaceNode::apply(ApplyParams applyParams) const while (current.ok()) { // Keep the _id if the replacement document does not have one. - if (!_containsId && current.getFieldName() == kIdFieldName) { + if (!replacementDocContainsIdField && current.getFieldName() == kIdFieldName) { current = current.rightSibling(); continue; } @@ -97,7 +94,7 @@ UpdateNode::ApplyResult ObjectReplaceNode::apply(ApplyParams applyParams) const } // Insert the provided contents instead. - for (auto&& elem : val) { + for (auto&& elem : replacementDoc) { invariant(applyParams.element.appendElement(elem)); } @@ -125,13 +122,14 @@ UpdateNode::ApplyResult ObjectReplaceNode::apply(ApplyParams applyParams) const newElem.getType() != BSONType::Array); } - auto oldElem = dotted_path_support::extractElementAtPath(original, (*path)->dottedField()); + auto oldElem = + dotted_path_support::extractElementAtPath(originalDoc, (*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, + << originalDoc, newElem.ok() || !oldElem.ok()); if (newElem.ok() && oldElem.ok()) { uassert(ErrorCodes::ImmutableField, @@ -155,4 +153,8 @@ UpdateNode::ApplyResult ObjectReplaceNode::apply(ApplyParams applyParams) const return ApplyResult(); } +UpdateExecutor::ApplyResult ObjectReplaceNode::applyUpdate(ApplyParams applyParams) const { + return applyReplacementUpdate(applyParams, val, _containsId); +} + } // namespace mongo diff --git a/src/mongo/db/update/object_replace_node.h b/src/mongo/db/update/object_replace_node.h index 340f0ec6c0b..c2c570f9093 100644 --- a/src/mongo/db/update/object_replace_node.h +++ b/src/mongo/db/update/object_replace_node.h @@ -34,52 +34,44 @@ #include <utility> #include <vector> -#include "mongo/db/update/update_node.h" +#include "mongo/db/update/update_executor.h" #include "mongo/stdx/memory.h" namespace mongo { /** - * An UpdateNode representing a replacement-style update. + * An UpdateExecutor representing a replacement-style update. */ -class ObjectReplaceNode : public UpdateNode { +class ObjectReplaceNode : public UpdateExecutor { public: + // Applies a replacement style update to 'applyParams.element'. If + // 'replacementDocContainsIdField' is false then the _id field from the original document will + // be preserved. + static ApplyResult applyReplacementUpdate(ApplyParams applyParams, + const BSONObj& replacementDoc, + bool replacementDocContainsIdField); + /** * Initializes the node with the document to replace with. Any zero-valued timestamps (except * for the _id) are updated to the current time. */ explicit ObjectReplaceNode(BSONObj value); - std::unique_ptr<UpdateNode> clone() const final { - return stdx::make_unique<ObjectReplaceNode>(*this); - } - void setCollator(const CollatorInterface* collator) final {} /** * Replaces the document that 'applyParams.element' belongs to with 'val'. If 'val' does not * contain an _id, the _id from the original document is preserved. 'applyParams.element' must - * be the root of the document. 'applyParams.pathToCreate' and 'applyParams.pathTaken' must be - * empty. Always returns a result stating that indexes are affected when the replacement is not - * a noop. + * be the root of the document. Always returns a result stating that indexes are affected when + * the replacement is not a noop. */ - ApplyResult apply(ApplyParams applyParams) const final; + ApplyResult applyUpdate(ApplyParams applyParams) const final; BSONObj serialize() const { return val; } - /** - * ObjectReplaceNode is never part of an update operator tree so this method cannot be called. - */ - virtual void produceSerializationMap( - FieldRef* currentPath, - std::map<std::string, std::vector<std::pair<std::string, BSONObj>>>* - operatorOrientedUpdates) const { - MONGO_UNREACHABLE; - } - void acceptVisitor(UpdateNodeVisitor* visitor) final { visitor->visit(this); } diff --git a/src/mongo/db/update/object_replace_node_test.cpp b/src/mongo/db/update/object_replace_node_test.cpp index fc3f26080a8..0a0e241cf0c 100644 --- a/src/mongo/db/update/object_replace_node_test.cpp +++ b/src/mongo/db/update/object_replace_node_test.cpp @@ -50,7 +50,7 @@ TEST_F(ObjectReplaceNodeTest, Noop) { ObjectReplaceNode node(obj); mutablebson::Document doc(fromjson("{a: 1, b: 2}")); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), doc); @@ -63,7 +63,7 @@ TEST_F(ObjectReplaceNodeTest, ShouldNotCreateIdIfNoIdExistsAndNoneIsSpecified) { ObjectReplaceNode node(obj); mutablebson::Document doc(fromjson("{c: 1, d: 2}")); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), doc); @@ -76,7 +76,7 @@ TEST_F(ObjectReplaceNodeTest, ShouldPreserveIdOfExistingDocumentIfIdNotSpecified ObjectReplaceNode node(obj); mutablebson::Document doc(fromjson("{_id: 0, c: 1, d: 2}")); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), doc); @@ -90,7 +90,7 @@ TEST_F(ObjectReplaceNodeTest, ShouldSucceedWhenImmutableIdIsNotModified) { mutablebson::Document doc(fromjson("{_id: 0, c: 1, d: 2}")); addImmutablePath("_id"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), doc); @@ -103,7 +103,7 @@ TEST_F(ObjectReplaceNodeTest, IdTimestampNotModified) { ObjectReplaceNode node(obj); mutablebson::Document doc(fromjson("{}")); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: Timestamp(0,0)}"), doc); @@ -116,7 +116,7 @@ TEST_F(ObjectReplaceNodeTest, NonIdTimestampsModified) { ObjectReplaceNode node(obj); mutablebson::Document doc(fromjson("{}")); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); @@ -143,7 +143,7 @@ TEST_F(ObjectReplaceNodeTest, ComplexDoc) { ObjectReplaceNode node(obj); mutablebson::Document doc(fromjson("{a: 1, b: [0, 2, 2], e: []}")); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], c: {d: 1}}"), doc); @@ -157,7 +157,7 @@ TEST_F(ObjectReplaceNodeTest, CannotRemoveImmutablePath) { mutablebson::Document doc(fromjson("{_id: 0, a: {b: 1}}")); addImmutablePath("a.b"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.applyUpdate(getApplyParams(doc.root())), AssertionException, ErrorCodes::ImmutableField, "After applying the update, the 'a.b' (required and immutable) " @@ -170,7 +170,7 @@ TEST_F(ObjectReplaceNodeTest, IdFieldIsNotRemoved) { mutablebson::Document doc(fromjson("{_id: 0, b: 1}")); addImmutablePath("_id"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: 1}"), doc); @@ -184,7 +184,7 @@ TEST_F(ObjectReplaceNodeTest, CannotReplaceImmutablePathWithArrayField) { mutablebson::Document doc(fromjson("{_id: 0, a: {b: 1}}")); addImmutablePath("a.b"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.applyUpdate(getApplyParams(doc.root())), AssertionException, ErrorCodes::NotSingleValueField, "After applying the update to the document, the (immutable) field " @@ -197,7 +197,7 @@ TEST_F(ObjectReplaceNodeTest, CannotMakeImmutablePathArrayDescendant) { mutablebson::Document doc(fromjson("{_id: 0, a: {'0': 1}}")); addImmutablePath("a.0"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.applyUpdate(getApplyParams(doc.root())), AssertionException, ErrorCodes::NotSingleValueField, "After applying the update to the document, the (immutable) field " @@ -210,7 +210,7 @@ TEST_F(ObjectReplaceNodeTest, CannotModifyImmutablePath) { mutablebson::Document doc(fromjson("{_id: 0, a: {b: 1}}")); addImmutablePath("a.b"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.applyUpdate(getApplyParams(doc.root())), AssertionException, ErrorCodes::ImmutableField, "After applying the update, the (immutable) field 'a.b' was found " @@ -223,7 +223,7 @@ TEST_F(ObjectReplaceNodeTest, CannotModifyImmutableId) { mutablebson::Document doc(fromjson("{_id: 0}")); addImmutablePath("_id"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.applyUpdate(getApplyParams(doc.root())), AssertionException, ErrorCodes::ImmutableField, "After applying the update, the (immutable) field '_id' was found " @@ -236,7 +236,7 @@ TEST_F(ObjectReplaceNodeTest, CanAddImmutableField) { mutablebson::Document doc(fromjson("{c: 1}")); addImmutablePath("a.b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 1}}"), doc); @@ -250,7 +250,7 @@ TEST_F(ObjectReplaceNodeTest, CanAddImmutableId) { mutablebson::Document doc(fromjson("{c: 1}")); addImmutablePath("_id"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0}"), doc); @@ -264,7 +264,7 @@ TEST_F(ObjectReplaceNodeTest, CannotCreateDollarPrefixedNameWhenValidateForStora mutablebson::Document doc(fromjson("{}")); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.applyUpdate(getApplyParams(doc.root())), AssertionException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); @@ -276,7 +276,7 @@ TEST_F(ObjectReplaceNodeTest, CanCreateDollarPrefixedNameWhenValidateForStorageI mutablebson::Document doc(fromjson("{}")); setValidateForStorage(false); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 1, $bad: 1}}"), doc); @@ -290,7 +290,7 @@ TEST_F(ObjectReplaceNodeTest, NoLogBuilder) { mutablebson::Document doc(fromjson("{b: 1}")); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.applyUpdate(getApplyParams(doc.root())); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); diff --git a/src/mongo/db/update/pipeline_executor.cpp b/src/mongo/db/update/pipeline_executor.cpp new file mode 100644 index 00000000000..d3360ffb2c4 --- /dev/null +++ b/src/mongo/db/update/pipeline_executor.cpp @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/update/pipeline_executor.h" + +#include "mongo/db/bson/dotted_path_support.h" +#include "mongo/db/pipeline/document_source_mock.h" +#include "mongo/db/pipeline/lite_parsed_pipeline.h" +#include "mongo/db/update/object_replace_node.h" +#include "mongo/db/update/storage_validation.h" + +namespace mongo { + +namespace { +constexpr StringData kIdFieldName = "_id"_sd; +} // namespace + +PipelineExecutor::PipelineExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx, + const std::vector<BSONObj>& pipeline) + : _expCtx(expCtx) { + // "Resolve" involved namespaces into a map. We have to populate this map so that any + // $lookups, etc. will not fail instantiation. They will not be used for execution as these + // stages are not allowed within an update context. + AggregationRequest aggRequest(NamespaceString("dummy.namespace"), pipeline); + LiteParsedPipeline liteParsedPipeline(aggRequest); + StringMap<ExpressionContext::ResolvedNamespace> resolvedNamespaces; + for (auto&& nss : liteParsedPipeline.getInvolvedNamespaces()) { + resolvedNamespaces.try_emplace(nss.coll(), nss, std::vector<BSONObj>{}); + } + _expCtx->setResolvedNamespaces(resolvedNamespaces); + _pipeline = uassertStatusOK(Pipeline::parse(pipeline, _expCtx)); + + // Validate the update pipeline. + for (auto&& stage : _pipeline->getSources()) { + auto stageConstraints = stage->constraints(); + uassert(ErrorCodes::InvalidOptions, + str::stream() << stage->getSourceName() + << " is not allowed to be used within an update", + stageConstraints.isAllowedWithinUpdatePipeline); + + invariant(stageConstraints.requiredPosition == + StageConstraints::PositionRequirement::kNone); + invariant(!stageConstraints.isIndependentOfAnyCollection); + } + + _pipeline->addInitialSource(DocumentSourceMock::create()); +} + +UpdateExecutor::ApplyResult PipelineExecutor::applyUpdate(ApplyParams applyParams) const { + DocumentSourceMock* mockStage = static_cast<DocumentSourceMock*>(_pipeline->peekFront()); + mockStage->queue.emplace_back(Document{applyParams.element.getDocument().getObject()}); + auto transformedDoc = _pipeline->getNext()->toBson(); + auto transformedDocHasIdField = transformedDoc.hasField(kIdFieldName); + + return ObjectReplaceNode::applyReplacementUpdate( + applyParams, transformedDoc, transformedDocHasIdField); +} + +} // namespace mongo diff --git a/src/mongo/db/update/pipeline_executor.h b/src/mongo/db/update/pipeline_executor.h new file mode 100644 index 00000000000..fb1c5d7a562 --- /dev/null +++ b/src/mongo/db/update/pipeline_executor.h @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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 <map> +#include <string> +#include <utility> +#include <vector> + +#include "mongo/db/pipeline/document_source.h" +#include "mongo/db/pipeline/pipeline.h" +#include "mongo/db/update/update_executor.h" +#include "mongo/stdx/memory.h" + +namespace mongo { + +/** + * An UpdateExecutor representing a pipeline-style update. + */ +class PipelineExecutor : public UpdateExecutor { + +public: + /** + * Initializes the node with an aggregation pipeline definition. + */ + explicit PipelineExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx, + const std::vector<BSONObj>& pipeline); + + /** + * Replaces the document that 'applyParams.element' belongs to with 'val'. If 'val' does not + * contain an _id, the _id from the original document is preserved. 'applyParams.element' must + * be the root of the document. Always returns a result stating that indexes are affected when + * the replacement is not a noop. + */ + ApplyResult applyUpdate(ApplyParams applyParams) const final; + + Value serialize() const { + std::vector<Value> valueArray; + for (const auto& stage : _pipeline->getSources()) { + // TODO SERVER-40539: Consider subclassing DocumentSourceQueue with a class that is + // explicitly skipped when serializing. With that change call Pipeline::serialize() + // directly. + if (stage->getSourceName() == "mock"_sd) { + continue; + } + + stage->serializeToArray(valueArray); + } + + return Value(valueArray); + } + + void acceptVisitor(UpdateNodeVisitor* visitor) final { + visitor->visit(this); + } + + void setCollator(const CollatorInterface* collator) final {} + +private: + boost::intrusive_ptr<ExpressionContext> _expCtx; + std::unique_ptr<Pipeline, PipelineDeleter> _pipeline; +}; + +} // namespace mongo diff --git a/src/mongo/db/update/pipeline_executor_test.cpp b/src/mongo/db/update/pipeline_executor_test.cpp new file mode 100644 index 00000000000..ad9a1da416c --- /dev/null +++ b/src/mongo/db/update/pipeline_executor_test.cpp @@ -0,0 +1,289 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/update/pipeline_executor.h" + +#include "mongo/bson/mutable/algorithm.h" +#include "mongo/bson/mutable/mutable_bson_test_utils.h" +#include "mongo/db/json.h" +#include "mongo/db/logical_clock.h" +#include "mongo/db/pipeline/document_value_test_util.h" +#include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/db/update/update_node_test_fixture.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using PipelineExecutorTest = UpdateNodeTest; +using mongo::mutablebson::Element; +using mongo::mutablebson::countChildren; + +TEST_F(PipelineExecutorTest, Noop) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {a: 1, b: 2}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{a: 1, b: 2}")); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_TRUE(result.noop); + ASSERT_FALSE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{a: 1, b: 2}"), doc); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, ShouldNotCreateIdIfNoIdExistsAndNoneIsSpecified) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {a: 1, b: 2}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{c: 1, d: 2}")); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{c: 1, d: 2, a: 1, b: 2}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{c: 1, d: 2, a: 1, b: 2}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, ShouldPreserveIdOfExistingDocumentIfIdNotReplaced) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {a: 1, b: 2}}"), + fromjson("{$project: {a: 1, b: 1, _id: 0}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, c: 1, d: 2}")); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{_id: 0, a: 1, b: 2}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, ShouldSucceedWhenImmutableIdIsNotModified) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 0, a: 1, b: 2}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, c: 1, d: 2}")); + addImmutablePath("_id"); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{_id: 0, c: 1, d: 2, a: 1, b: 2}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{_id: 0, c: 1, d: 2, a: 1, b: 2}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, ComplexDoc) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {a: 1, b: [0, 1, 2], c: {d: 1}}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{a: 1, b: [0, 2, 2], e: []}")); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], e: [], c: {d: 1}}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{a: 1, b: [0, 1, 2], e: [], c: {d: 1}}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, CannotRemoveImmutablePath) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$project: {c: 1}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, a: {b: 1}}")); + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(exec.applyUpdate(getApplyParams(doc.root())), + AssertionException, + ErrorCodes::ImmutableField, + "After applying the update, the 'a.b' (required and immutable) " + "field was found to have been removed --{ _id: 0, a: { b: 1 } }"); +} + + +TEST_F(PipelineExecutorTest, IdFieldIsNotRemoved) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$project: {a: 1, _id: 0}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, b: 1}")); + addImmutablePath("_id"); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{_id: 0}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{_id: 0}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, CannotReplaceImmutablePathWithArrayField) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 0, a: [{b: 1}]}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, a: {b: 1}}")); + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(exec.applyUpdate(getApplyParams(doc.root())), + AssertionException, + ErrorCodes::NotSingleValueField, + "After applying the update to the document, the (immutable) field " + "'a.b' was found to be an array or array descendant."); +} + +TEST_F(PipelineExecutorTest, CannotMakeImmutablePathArrayDescendant) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 0, a: [1]}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, a: {'0': 1}}")); + addImmutablePath("a.0"); + ASSERT_THROWS_CODE_AND_WHAT(exec.applyUpdate(getApplyParams(doc.root())), + AssertionException, + ErrorCodes::NotSingleValueField, + "After applying the update to the document, the (immutable) field " + "'a.0' was found to be an array or array descendant."); +} + +TEST_F(PipelineExecutorTest, CannotModifyImmutablePath) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 0, a: {b: 2}}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0, a: {b: 1}}")); + addImmutablePath("a.b"); + ASSERT_THROWS_CODE_AND_WHAT(exec.applyUpdate(getApplyParams(doc.root())), + AssertionException, + ErrorCodes::ImmutableField, + "After applying the update, the (immutable) field 'a.b' was found " + "to have been altered to b: 2"); +} + +TEST_F(PipelineExecutorTest, CannotModifyImmutableId) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 1}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{_id: 0}")); + addImmutablePath("_id"); + ASSERT_THROWS_CODE_AND_WHAT(exec.applyUpdate(getApplyParams(doc.root())), + AssertionException, + ErrorCodes::ImmutableField, + "After applying the update, the (immutable) field '_id' was found " + "to have been altered to _id: 1"); +} + +TEST_F(PipelineExecutorTest, CanAddImmutableField) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {a: {b: 1}}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{c: 1}")); + addImmutablePath("a.b"); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{c: 1, a: {b: 1}}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{c: 1, a: {b: 1}}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, CanAddImmutableId) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 0}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{c: 1}")); + addImmutablePath("_id"); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{c: 1, _id: 0}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + ASSERT_EQUALS(fromjson("{c: 1, _id: 0}"), getLogDoc()); +} + +TEST_F(PipelineExecutorTest, CannotCreateDollarPrefixedName) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {'a.$bad': 1}}")}; + ASSERT_THROWS_CODE(PipelineExecutor(expCtx, pipeline), AssertionException, 16410); +} + +TEST_F(PipelineExecutorTest, NoLogBuilder) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {a: 1}}")}; + PipelineExecutor exec(expCtx, pipeline); + + mutablebson::Document doc(fromjson("{b: 1}")); + setLogBuilderToNull(); + auto result = exec.applyUpdate(getApplyParams(doc.root())); + ASSERT_FALSE(result.noop); + ASSERT_TRUE(result.indexesAffected); + ASSERT_EQUALS(fromjson("{b: 1, a: 1}"), doc); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST_F(PipelineExecutorTest, SerializeTest) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + + std::vector<BSONObj> pipeline{fromjson("{$addFields: {_id: 0, a: [{b: 1}]}}"), + fromjson("{$project: {a: 1}}"), + fromjson("{$replaceRoot: {newRoot: '$foo'}}")}; + PipelineExecutor exec(expCtx, pipeline); + + auto serialized = exec.serialize(); + BSONObj doc( + fromjson("[{$addFields: {_id: {$const: 0}, a: [{b: {$const: 1}}]}}, {$project: { _id: " + "true, a: true}}, {$replaceRoot: {newRoot: '$foo'}}]")); + ASSERT_VALUE_EQ(serialized, Value(BSONArray(doc))); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/update/pop_node_test.cpp b/src/mongo/db/update/pop_node_test.cpp index ebf92abc81f..1a735a3d0f5 100644 --- a/src/mongo/db/update/pop_node_test.cpp +++ b/src/mongo/db/update/pop_node_test.cpp @@ -111,7 +111,7 @@ TEST_F(PopNodeTest, NoopWhenFirstPathComponentDoesNotExist) { mmb::Document doc(fromjson("{b: [1, 2, 3]}")); setPathToCreate("a.b"); addIndexedPath("a.b"); - auto result = popNode.apply(getApplyParams(doc.root())); + auto result = popNode.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: [1, 2, 3]}"), doc); @@ -129,7 +129,7 @@ TEST_F(PopNodeTest, NoopWhenPathPartiallyExists) { setPathToCreate("b.c"); setPathTaken("a"); addIndexedPath("a.b.c"); - auto result = popNode.apply(getApplyParams(doc.root()["a"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}}"), doc); @@ -147,7 +147,7 @@ TEST_F(PopNodeTest, NoopWhenNumericalPathComponentExceedsArrayLength) { setPathToCreate("0"); setPathTaken("a"); addIndexedPath("a.0"); - auto result = popNode.apply(getApplyParams(doc.root()["a"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -166,7 +166,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsBlockedByAScalar) { setPathTaken("a"); addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(getApplyParams(doc.root()["a"])), + popNode.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::PathNotViable, "Cannot use the part (b) of (a.b) to traverse the element ({a: \"foo\"})"); @@ -183,7 +183,7 @@ DEATH_TEST_F(PopNodeTest, mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - popNode.apply(getApplyParams(doc.end())); + popNode.apply(getApplyParams(doc.end()), getUpdateNodeApplyParams()); } TEST_F(PopNodeTest, ThrowsWhenPathExistsButDoesNotContainAnArray) { @@ -195,10 +195,11 @@ TEST_F(PopNodeTest, ThrowsWhenPathExistsButDoesNotContainAnArray) { mmb::Document doc(fromjson("{a: {b: 'foo'}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - ASSERT_THROWS_CODE_AND_WHAT(popNode.apply(getApplyParams(doc.root()["a"]["b"])), - AssertionException, - ErrorCodes::TypeMismatch, - "Path 'a.b' contains an element of non-array type 'string'"); + ASSERT_THROWS_CODE_AND_WHAT( + popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::TypeMismatch, + "Path 'a.b' contains an element of non-array type 'string'"); } TEST_F(PopNodeTest, NoopWhenPathContainsAnEmptyArray) { @@ -210,7 +211,7 @@ TEST_F(PopNodeTest, NoopWhenPathContainsAnEmptyArray) { mmb::Document doc(fromjson("{a: {b: []}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); @@ -228,7 +229,7 @@ TEST_F(PopNodeTest, PopsSingleElementFromTheBack) { mmb::Document doc(fromjson("{a: {b: [1]}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); @@ -246,7 +247,7 @@ TEST_F(PopNodeTest, PopsSingleElementFromTheFront) { mmb::Document doc(fromjson("{a: {b: [[1]]}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); @@ -264,7 +265,7 @@ TEST_F(PopNodeTest, PopsFromTheBackOfMultiElementArray) { mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); setPathTaken("a.b"); addIndexedPath("a.b.c"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); @@ -282,7 +283,7 @@ TEST_F(PopNodeTest, PopsFromTheFrontOfMultiElementArray) { mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [2, 3]}}"), doc); @@ -300,7 +301,7 @@ TEST_F(PopNodeTest, PopsFromTheFrontOfMultiElementArrayWithoutAffectingIndexes) mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); setPathTaken("a.b"); addIndexedPath("unrelated.path"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [2, 3]}}"), doc); @@ -317,7 +318,7 @@ TEST_F(PopNodeTest, SucceedsWithNullUpdateIndexData) { mmb::Document doc(fromjson("{a: {b: [1, 2, 3]}}")); setPathTaken("a.b"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); @@ -336,7 +337,7 @@ TEST_F(PopNodeTest, SucceedsWithNullLogBuilder) { setPathTaken("a.b"); addIndexedPath("a.b.c"); setLogBuilderToNull(); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [1, 2]}}"), doc); @@ -354,7 +355,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsImmutable) { addImmutablePath("a.b"); addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(getApplyParams(doc.root()["a"]["b"])), + popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); @@ -376,7 +377,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsPrefixOfImmutable) { addImmutablePath("a.0"); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(getApplyParams(doc.root()["a"])), + popNode.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a' would modify the immutable field 'a.0'"); @@ -393,7 +394,7 @@ TEST_F(PopNodeTest, ThrowsWhenPathIsSuffixOfImmutable) { addImmutablePath("a"); addIndexedPath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - popNode.apply(getApplyParams(doc.root()["a"]["b"])), + popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b' would modify the immutable field 'a'"); @@ -409,7 +410,7 @@ TEST_F(PopNodeTest, NoopOnImmutablePathSucceeds) { setPathTaken("a.b"); addImmutablePath("a.b"); addIndexedPath("a.b"); - auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = popNode.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); diff --git a/src/mongo/db/update/pull_node_test.cpp b/src/mongo/db/update/pull_node_test.cpp index 6c191f0ed99..b9092a98927 100644 --- a/src/mongo/db/update/pull_node_test.cpp +++ b/src/mongo/db/update/pull_node_test.cpp @@ -139,7 +139,7 @@ TEST_F(PullNodeTest, TargetNotFound) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -156,10 +156,11 @@ TEST_F(PullNodeTest, ApplyToStringFails) { mutablebson::Document doc(fromjson("{a: 'foo'}")); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::BadValue, - "Cannot apply $pull to a non-array value"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "Cannot apply $pull to a non-array value"); } TEST_F(PullNodeTest, ApplyToObjectFails) { @@ -171,10 +172,11 @@ TEST_F(PullNodeTest, ApplyToObjectFails) { mutablebson::Document doc(fromjson("{a: {foo: 'bar'}}")); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::BadValue, - "Cannot apply $pull to a non-array value"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "Cannot apply $pull to a non-array value"); } TEST_F(PullNodeTest, ApplyToNonViablePathFails) { @@ -188,7 +190,7 @@ TEST_F(PullNodeTest, ApplyToNonViablePathFails) { setPathTaken("a"); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"])), + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::PathNotViable, "Cannot use the part (b) of (a.b) to traverse the element ({a: 1})"); @@ -204,7 +206,7 @@ TEST_F(PullNodeTest, ApplyToMissingElement) { setPathToCreate("d"); setPathTaken("a.b.c"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {c: {}}}}"), doc); @@ -221,7 +223,7 @@ TEST_F(PullNodeTest, ApplyToEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -238,7 +240,7 @@ TEST_F(PullNodeTest, ApplyToArrayMatchingNone) { mutablebson::Document doc(fromjson("{a: [2, 3, 4, 5]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2, 3, 4, 5]}"), doc); @@ -255,7 +257,7 @@ TEST_F(PullNodeTest, ApplyToArrayMatchingOne) { mutablebson::Document doc(fromjson("{a: [0, 1, 2, 3]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2, 3]}"), doc); @@ -272,7 +274,7 @@ TEST_F(PullNodeTest, ApplyToArrayMatchingSeveral) { mutablebson::Document doc(fromjson("{a: [0, 1, 0, 2, 0, 3, 0, 4, 0, 5]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2, 3, 4, 5]}"), doc); @@ -289,7 +291,7 @@ TEST_F(PullNodeTest, ApplyToArrayMatchingAll) { mutablebson::Document doc(fromjson("{a: [0, -1, -2, -3, -4, -5]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -306,7 +308,7 @@ TEST_F(PullNodeTest, ApplyNoIndexDataNoLogBuilder) { mutablebson::Document doc(fromjson("{a: [0, 1, 2, 3]}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2, 3]}"), doc); @@ -326,7 +328,7 @@ TEST_F(PullNodeTest, ApplyWithCollation) { mutablebson::Document doc(fromjson("{a: ['zaa', 'zcc', 'zbb', 'zee']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['zaa', 'zbb']}"), doc); @@ -345,7 +347,7 @@ TEST_F(PullNodeTest, ApplyWithCollationDoesNotAffectNonStringMatches) { mutablebson::Document doc(fromjson("{a: [2, 1, 0, -1, -2, -3]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2, 1]}"), doc); @@ -364,7 +366,7 @@ TEST_F(PullNodeTest, ApplyWithCollationDoesNotAffectRegexMatches) { mutablebson::Document doc(fromjson("{a: ['b', 'a', 'aab', 'cb', 'bba']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['b', 'cb']}"), doc); @@ -383,7 +385,7 @@ TEST_F(PullNodeTest, ApplyStringLiteralMatchWithCollation) { mutablebson::Document doc(fromjson("{a: ['b', 'a', 'aab', 'cb', 'bba']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -402,7 +404,7 @@ TEST_F(PullNodeTest, ApplyCollationDoesNotAffectNumberLiteralMatches) { mutablebson::Document doc(fromjson("{a: ['a', 99, 'b', 2, 'c', 99, 'd']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b', 2, 'c', 'd']}"), doc); @@ -419,7 +421,7 @@ TEST_F(PullNodeTest, ApplyStringMatchAfterSetCollator) { // First without a collator. mutablebson::Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b', 'd']}"), doc); @@ -431,7 +433,7 @@ TEST_F(PullNodeTest, ApplyStringMatchAfterSetCollator) { mutablebson::Document doc2(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); resetApplyParams(); setPathTaken("a"); - result = node.apply(getApplyParams(doc2.root()["a"])); + result = node.apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc2); @@ -447,7 +449,7 @@ TEST_F(PullNodeTest, ApplyElementMatchAfterSetCollator) { // First without a collator. mutablebson::Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b']}"), doc); @@ -459,7 +461,7 @@ TEST_F(PullNodeTest, ApplyElementMatchAfterSetCollator) { mutablebson::Document doc2(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); resetApplyParams(); setPathTaken("a"); - result = node.apply(getApplyParams(doc2.root()["a"])); + result = node.apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc2); @@ -475,7 +477,7 @@ TEST_F(PullNodeTest, ApplyObjectMatchAfterSetCollator) { // First without a collator. mutablebson::Document doc(fromjson("{a : [{b: 'w'}, {b: 'x'}, {b: 'y'}, {b: 'z'}]}")); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a : [{b: 'w'}, {b: 'x'}, {b: 'z'}]}"), doc); @@ -487,7 +489,7 @@ TEST_F(PullNodeTest, ApplyObjectMatchAfterSetCollator) { mutablebson::Document doc2(fromjson("{a : [{b: 'w'}, {b: 'x'}, {b: 'y'}, {b: 'z'}]}")); resetApplyParams(); setPathTaken("a"); - result = node.apply(getApplyParams(doc2.root()["a"])); + result = node.apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc2); @@ -508,7 +510,7 @@ TEST_F(PullNodeTest, SetCollatorDoesNotAffectClone) { // The original node should now have collation. mutablebson::Document doc(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -518,7 +520,7 @@ TEST_F(PullNodeTest, SetCollatorDoesNotAffectClone) { mutablebson::Document doc2(fromjson("{ a : ['a', 'b', 'c', 'd'] }")); resetApplyParams(); setPathTaken("a"); - result = cloneNode->apply(getApplyParams(doc2.root()["a"])); + result = cloneNode->apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', 'b', 'd']}"), doc2); @@ -538,7 +540,7 @@ TEST_F(PullNodeTest, ApplyComplexDocAndMatching1) { mutablebson::Document doc(fromjson("{a: {b: [{x: 1}, {y: 'y'}, {x: 2}, {z: 'z'}]}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [{x: 1}, {x: 2}]}}"), doc); @@ -555,7 +557,7 @@ TEST_F(PullNodeTest, ApplyComplexDocAndMatching2) { mutablebson::Document doc(fromjson("{a: {b: [{x: 1}, {y: 'y'}, {x: 2}, {z: 'z'}]}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [{x: 1}, {x: 2}, {z: 'z'}]}}"), doc); @@ -572,7 +574,7 @@ TEST_F(PullNodeTest, ApplyComplexDocAndMatching3) { mutablebson::Document doc(fromjson("{a: {b: [{x: 1}, {y: 'y'}, {x: 2}, {z: 'z'}]}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: [{x: 2}, {z: 'z'}]}}"), doc); @@ -592,7 +594,7 @@ TEST_F(PullNodeTest, ApplyFullPredicateWithCollation) { fromjson("{a: {b: [{x: 'foo', y: 1}, {x: 'bar', y: 2}, {x: 'baz', y: 3}]}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: []}}"), doc); @@ -609,7 +611,7 @@ TEST_F(PullNodeTest, ApplyScalarValueMod) { mutablebson::Document doc(fromjson("{a: [1, 2, 1, 2, 1, 2]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2, 2, 2]}"), doc); @@ -626,7 +628,7 @@ TEST_F(PullNodeTest, ApplyObjectValueMod) { mutablebson::Document doc(fromjson("{a: [{x: 1}, {y: 2}, {x: 1}, {y: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{x: 1}, {x: 1}]}"), doc); @@ -644,7 +646,7 @@ TEST_F(PullNodeTest, DocumentationExample1) { fromjson("{flags: ['vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce']}")); setPathTaken("flags"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["flags"])); + auto result = node.apply(getApplyParams(doc.root()["flags"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{flags: ['vme', 'de', 'pse', 'tsc', 'pae', 'mce']}"), doc); @@ -662,7 +664,7 @@ TEST_F(PullNodeTest, DocumentationExample2a) { mutablebson::Document doc(fromjson("{votes: [3, 5, 6, 7, 7, 8]}")); setPathTaken("votes"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["votes"])); + auto result = node.apply(getApplyParams(doc.root()["votes"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{votes: [3, 5, 6, 8]}"), doc); @@ -679,7 +681,7 @@ TEST_F(PullNodeTest, DocumentationExample2b) { mutablebson::Document doc(fromjson("{votes: [3, 5, 6, 7, 7, 8]}")); setPathTaken("votes"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["votes"])); + auto result = node.apply(getApplyParams(doc.root()["votes"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{votes: [3, 5, 6]}"), doc); @@ -696,7 +698,7 @@ TEST_F(PullNodeTest, ApplyPullWithObjectValueToArrayWithNonObjectValue) { mutablebson::Document doc(fromjson("{a: [{x: 1}, 2]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [2]}"), doc); @@ -714,7 +716,7 @@ TEST_F(PullNodeTest, CannotModifyImmutableField) { setPathTaken("_id.a"); addImmutablePath("_id"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["_id"]["a"])), + node.apply(getApplyParams(doc.root()["_id"]["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path '_id.a' would modify the immutable field '_id'"); @@ -729,7 +731,7 @@ TEST_F(PullNodeTest, SERVER_3988) { mutablebson::Document doc(fromjson("{x: 1, y: [2, 3, 4, 'abc', 'xyz']}")); setPathTaken("y"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["y"])); + auto result = node.apply(getApplyParams(doc.root()["y"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{x: 1, y: [2, 3, 4, 'abc']}"), doc); diff --git a/src/mongo/db/update/pullall_node_test.cpp b/src/mongo/db/update/pullall_node_test.cpp index ba6d3697634..60b09e7b77d 100644 --- a/src/mongo/db/update/pullall_node_test.cpp +++ b/src/mongo/db/update/pullall_node_test.cpp @@ -92,7 +92,7 @@ TEST_F(PullAllNodeTest, TargetNotFound) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathToCreate("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 'a', {r: 1, b: 2}]}"), doc); @@ -110,7 +110,7 @@ TEST_F(PullAllNodeTest, TargetArrayElementNotFound) { setPathToCreate("2"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc); @@ -127,10 +127,11 @@ TEST_F(PullAllNodeTest, ApplyToNonArrayFails) { mutablebson::Document doc(fromjson("{a: [1, 2]}")); setPathTaken("a.0"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"][0])), - AssertionException, - ErrorCodes::BadValue, - "Cannot apply $pull to a non-array value"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"][0]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "Cannot apply $pull to a non-array value"); } TEST_F(PullAllNodeTest, ApplyWithSingleNumber) { @@ -142,7 +143,7 @@ TEST_F(PullAllNodeTest, ApplyWithSingleNumber) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', {r: 1, b: 2}]}"), doc); @@ -159,7 +160,7 @@ TEST_F(PullAllNodeTest, ApplyNoIndexDataNoLogBuilder) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['a', {r: 1, b: 2}]}"), doc); @@ -175,7 +176,7 @@ TEST_F(PullAllNodeTest, ApplyWithElementNotPresentInArray) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 'a', {r: 1, b: 2}]}"), doc); @@ -192,7 +193,7 @@ TEST_F(PullAllNodeTest, ApplyWithWithTwoElements) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{r: 1, b: 2}]}"), doc); @@ -209,7 +210,7 @@ TEST_F(PullAllNodeTest, ApplyWithAllArrayElements) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -226,7 +227,7 @@ TEST_F(PullAllNodeTest, ApplyWithAllArrayElementsButOutOfOrder) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -243,7 +244,7 @@ TEST_F(PullAllNodeTest, ApplyWithAllArrayElementsAndThenSome) { mutablebson::Document doc(fromjson("{a: [1, 'a', {r: 1, b: 2}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -262,7 +263,7 @@ TEST_F(PullAllNodeTest, ApplyWithCollator) { mutablebson::Document doc(fromjson("{a: ['foo', 'bar', 'baz']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['baz']}"), doc); @@ -279,7 +280,7 @@ TEST_F(PullAllNodeTest, ApplyAfterSetCollator) { // First without a collator. mutablebson::Document doc(fromjson("{a: ['foo', 'bar', 'baz']}")); setPathTaken("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: ['foo', 'bar', 'baz']}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); @@ -290,7 +291,7 @@ TEST_F(PullAllNodeTest, ApplyAfterSetCollator) { mutablebson::Document doc2(fromjson("{a: ['foo', 'bar', 'baz']}")); resetApplyParams(); setPathTaken("a"); - result = node.apply(getApplyParams(doc2.root()["a"])); + result = node.apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['baz']}"), doc2); diff --git a/src/mongo/db/update/push_node_test.cpp b/src/mongo/db/update/push_node_test.cpp index 704108d96ce..d0ef73e22e5 100644 --- a/src/mongo/db/update/push_node_test.cpp +++ b/src/mongo/db/update/push_node_test.cpp @@ -267,7 +267,7 @@ TEST_F(PushNodeTest, ApplyToNonArrayFails) { setPathTaken("a"); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"])), + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "The field 'a' must be an array but is of type int in document {_id: \"test_object\"}"); @@ -282,7 +282,7 @@ TEST_F(PushNodeTest, ApplyToEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -300,7 +300,7 @@ TEST_F(PushNodeTest, ApplyToEmptyDocument) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -318,7 +318,7 @@ TEST_F(PushNodeTest, ApplyToArrayWithOneElement) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -342,7 +342,8 @@ TEST_F(PushNodeTest, ApplyToDottedPathElement) { setPathToCreate("votes"); setPathTaken("choices.first"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["choices"]["first"])); + auto result = + node.apply(getApplyParams(doc.root()["choices"]["first"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 1, " @@ -365,7 +366,7 @@ TEST_F(PushNodeTest, ApplySimpleEachToEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -383,7 +384,7 @@ TEST_F(PushNodeTest, ApplySimpleEachToEmptyDocument) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -401,7 +402,7 @@ TEST_F(PushNodeTest, ApplyMultipleEachToEmptyDocument) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 2]}"), doc); @@ -419,7 +420,7 @@ TEST_F(PushNodeTest, ApplySimpleEachToArrayWithOneElement) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -437,7 +438,7 @@ TEST_F(PushNodeTest, ApplyMultipleEachToArrayWithOneElement) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1, 2]}"), doc); @@ -455,7 +456,7 @@ TEST_F(PushNodeTest, ApplyEmptyEachToEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -473,7 +474,7 @@ TEST_F(PushNodeTest, ApplyEmptyEachToEmptyDocument) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -491,7 +492,7 @@ TEST_F(PushNodeTest, ApplyEmptyEachToArrayWithOneElement) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); @@ -509,7 +510,7 @@ TEST_F(PushNodeTest, ApplyToArrayWithSlice) { mutablebson::Document doc(fromjson("{a: [3]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [3]}"), doc); @@ -527,7 +528,7 @@ TEST_F(PushNodeTest, ApplyWithNumericSort) { mutablebson::Document doc(fromjson("{a: [3]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [-1, 2, 3]}"), doc); @@ -545,7 +546,7 @@ TEST_F(PushNodeTest, ApplyWithReverseNumericSort) { mutablebson::Document doc(fromjson("{a: [3]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [4, 3, -1]}"), doc); @@ -563,7 +564,7 @@ TEST_F(PushNodeTest, ApplyWithMixedSort) { mutablebson::Document doc(fromjson("{a: [3, 't', {b: 1}, {a: 1}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [-1, 3, 4, 't', {a: 1}, {b: 1}]}"), doc); @@ -581,7 +582,7 @@ TEST_F(PushNodeTest, ApplyWithReverseMixedSort) { mutablebson::Document doc(fromjson("{a: [3, 't', {b: 1}, {a: 1}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 1}, {a: 1}, 't', 4, 3, -1]}"), doc); @@ -599,7 +600,7 @@ TEST_F(PushNodeTest, ApplyWithEmbeddedFieldSort) { mutablebson::Document doc(fromjson("{a: [3, 't', {b: 1}, {a: 1}]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [3, 't', {b: 1}, 4, -1, {a: 1}]}"), doc); @@ -619,7 +620,7 @@ TEST_F(PushNodeTest, ApplySortWithCollator) { mutablebson::Document doc(fromjson("{a: ['dd', 'fc', 'gb']}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: ['ha', 'gb', 'fc', 'dd']}"), doc); @@ -637,7 +638,7 @@ TEST_F(PushNodeTest, ApplySortAfterSetCollator) { mutablebson::Document doc(fromjson("{a: ['dd', 'fc', 'gb']}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: ['dd', 'fc', 'gb', 'ha']}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); @@ -650,7 +651,7 @@ TEST_F(PushNodeTest, ApplySortAfterSetCollator) { CollatorInterfaceMock mockCollator(CollatorInterfaceMock::MockType::kReverseString); node.setCollator(&mockCollator); mutablebson::Document doc2(fromjson("{a: ['dd', 'fc', 'gb']}")); - result = node.apply(getApplyParams(doc2.root()["a"])); + result = node.apply(getApplyParams(doc2.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: ['ha', 'gb', 'fc', 'dd']}"), doc2); ASSERT_FALSE(doc2.isInPlaceModeEnabled()); @@ -662,7 +663,7 @@ TEST_F(PushNodeTest, ApplySortAfterSetCollator) { void checkDocumentAndResult(BSONObj updateModifier, BSONObj expectedDocument, const mutablebson::Document& actualDocument, - UpdateNode::ApplyResult applyResult) { + UpdateExecutor::ApplyResult applyResult) { if (expectedDocument == actualDocument && !applyResult.noop && !applyResult.indexesAffected) { // Check succeeded. } else { @@ -704,7 +705,7 @@ TEST_F(PushNodeTest, ApplyToEmptyArrayWithSliceValues) { resetApplyParams(); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); checkDocumentAndResult(update, data.resultingDoc, doc, result); } } @@ -737,7 +738,7 @@ TEST_F(PushNodeTest, ApplyToPopulatedArrayWithSliceValues) { resetApplyParams(); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); checkDocumentAndResult(update, data.resultingDoc, doc, result); } } @@ -839,7 +840,7 @@ TEST_F(PushNodeTest, ApplyToPopulatedArrayWithSortAndSliceValues) { resetApplyParams(); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); checkDocumentAndResult(update, data.resultingDoc, doc, result); } } @@ -853,7 +854,7 @@ TEST_F(PushNodeTest, ApplyToEmptyArrayWithPositionZero) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -871,7 +872,7 @@ TEST_F(PushNodeTest, ApplyToEmptyArrayWithPositionOne) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -889,7 +890,7 @@ TEST_F(PushNodeTest, ApplyToEmptyArrayWithLargePosition) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -907,7 +908,7 @@ TEST_F(PushNodeTest, ApplyToSingletonArrayWithPositionZero) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc); @@ -925,7 +926,7 @@ TEST_F(PushNodeTest, ApplyToSingletonArrayWithLargePosition) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1]}"), doc); @@ -943,7 +944,7 @@ TEST_F(PushNodeTest, ApplyToEmptyArrayWithNegativePosition) { mutablebson::Document doc(fromjson("{a: []}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); @@ -961,7 +962,7 @@ TEST_F(PushNodeTest, ApplyToSingletonArrayWithNegativePosition) { mutablebson::Document doc(fromjson("{a: [0]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [1, 0]}"), doc); @@ -979,7 +980,7 @@ TEST_F(PushNodeTest, ApplyToPopulatedArrayWithNegativePosition) { mutablebson::Document doc(fromjson("{a: [0, 1, 2, 3, 4]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1, 2, 5, 3, 4]}"), doc); @@ -997,7 +998,7 @@ TEST_F(PushNodeTest, ApplyToPopulatedArrayWithOutOfBoundsNegativePosition) { mutablebson::Document doc(fromjson("{a: [0, 1, 2, 3, 4]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [5, 0, 1, 2, 3, 4]}"), doc); @@ -1015,7 +1016,7 @@ TEST_F(PushNodeTest, ApplyMultipleElementsPushWithNegativePosition) { mutablebson::Document doc(fromjson("{a: [0, 1, 2, 3, 4]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 1, 2, 5, 6, 7, 3, 4]}"), doc); @@ -1035,7 +1036,7 @@ TEST_F(PushNodeTest, PushWithMinIntAsPosition) { mutablebson::Document doc(fromjson("{a: [0, 1, 2, 3, 4]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [5, 0, 1, 2, 3, 4]}"), doc); diff --git a/src/mongo/db/update/rename_node.cpp b/src/mongo/db/update/rename_node.cpp index 75815485982..4177a5f446d 100644 --- a/src/mongo/db/update/rename_node.cpp +++ b/src/mongo/db/update/rename_node.cpp @@ -162,7 +162,8 @@ Status RenameNode::init(BSONElement modExpr, return Status::OK(); } -UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { +UpdateExecutor::ApplyResult RenameNode::apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const { // It would make sense to store fromFieldRef and toFieldRef as members during // RenameNode::init(), but FieldRef is not copyable. auto fromFieldRef = std::make_shared<FieldRef>(_val.fieldName()); @@ -214,8 +215,9 @@ UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { // Check that our destination path does not contain an array. (If the rename will overwrite an // existing element, that element may be an array. Iff pathToCreate is empty, "element" // represents an element that we are going to overwrite.) - for (auto currentElement = applyParams.pathToCreate->empty() ? applyParams.element.parent() - : applyParams.element; + for (auto currentElement = updateNodeApplyParams.pathToCreate->empty() + ? applyParams.element.parent() + : applyParams.element; currentElement != document.root(); currentElement = currentElement.parent()) { invariant(currentElement.ok()); @@ -237,15 +239,17 @@ UpdateNode::ApplyResult RenameNode::apply(ApplyParams applyParams) const { // should call the init() method of a ModifierNode before calling its apply() method, but the // init() methods of SetElementNode and UnsetNode don't do anything, so we can skip them. SetElementNode setElement(fromElement); - auto setElementApplyResult = setElement.apply(applyParams); + auto setElementApplyResult = setElement.apply(applyParams, updateNodeApplyParams); ApplyParams unsetParams(applyParams); unsetParams.element = fromElement; - unsetParams.pathToCreate = std::make_shared<FieldRef>(); - unsetParams.pathTaken = fromFieldRef; + + UpdateNodeApplyParams unsetUpdateNodeApplyParams; + unsetUpdateNodeApplyParams.pathToCreate = std::make_shared<FieldRef>(); + unsetUpdateNodeApplyParams.pathTaken = fromFieldRef; UnsetNode unsetElement; - auto unsetElementApplyResult = unsetElement.apply(unsetParams); + auto unsetElementApplyResult = unsetElement.apply(unsetParams, unsetUpdateNodeApplyParams); // Determine the final result based on the results of the $set and $unset. ApplyResult applyResult; diff --git a/src/mongo/db/update/rename_node.h b/src/mongo/db/update/rename_node.h index c949f93a050..5dd23287e8d 100644 --- a/src/mongo/db/update/rename_node.h +++ b/src/mongo/db/update/rename_node.h @@ -57,7 +57,8 @@ public: void setCollator(const CollatorInterface* collator) final {} - ApplyResult apply(ApplyParams applyParams) const final; + ApplyResult apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const final; void produceSerializationMap( FieldRef* currentPath, diff --git a/src/mongo/db/update/rename_node_test.cpp b/src/mongo/db/update/rename_node_test.cpp index f9fe8aebd5a..93ddfd61714 100644 --- a/src/mongo/db/update/rename_node_test.cpp +++ b/src/mongo/db/update/rename_node_test.cpp @@ -119,7 +119,7 @@ TEST_F(RenameNodeTest, SimpleNumberAtRoot) { mutablebson::Document doc(fromjson("{a: 2}")); setPathToCreate("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); @@ -136,7 +136,7 @@ TEST_F(RenameNodeTest, ToExistsAtSameLevel) { mutablebson::Document doc(fromjson("{a: 2, b: 1}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); @@ -153,7 +153,7 @@ TEST_F(RenameNodeTest, ToAndFromHaveSameValue) { mutablebson::Document doc(fromjson("{a: 2, b: 2}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); @@ -170,7 +170,7 @@ TEST_F(RenameNodeTest, RenameToFieldWithSameValueButDifferentType) { mutablebson::Document doc(fromjson("{a: 1, b: NumberLong(1)}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1}"), doc); @@ -187,7 +187,7 @@ TEST_F(RenameNodeTest, FromDottedElement) { mutablebson::Document doc(fromjson("{a: {c: {d: 6}}, b: 1}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}, b: {d: 6}}"), doc); @@ -204,7 +204,7 @@ TEST_F(RenameNodeTest, RenameToExistingNestedFieldDoesNotReorderFields) { mutablebson::Document doc(fromjson("{a: {b: {c: 1, d: 2}}, b: 3, c: {d: 4}}")); setPathTaken("a.b.c"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {c: 4, d: 2}}, b: 3, c: {}}"), doc); @@ -222,7 +222,7 @@ TEST_F(RenameNodeTest, MissingCompleteTo) { setPathToCreate("r.d"); setPathTaken("c"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["c"])); + auto result = node.apply(getApplyParams(doc.root()["c"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, c: {r: {d: 2}}}"), doc); @@ -239,7 +239,7 @@ TEST_F(RenameNodeTest, ToIsCompletelyMissing) { mutablebson::Document doc(fromjson("{a: 2}")); setPathToCreate("b.c.d"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: {c: {d: 2}}}"), doc); @@ -256,7 +256,7 @@ TEST_F(RenameNodeTest, ToMissingDottedField) { mutablebson::Document doc(fromjson("{a: [{a:2, b:1}]}")); setPathToCreate("b.c.d"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: {c: {d: [{a:2, b:1}]}}}"), doc); @@ -274,11 +274,12 @@ TEST_F(RenameNodeTest, MoveIntoArray) { setPathToCreate("2"); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::BadValue, - "The destination field cannot be an array element, 'a.2' in doc " - "with _id: \"test_object\" has an array field called 'a'"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "The destination field cannot be an array element, 'a.2' in doc " + "with _id: \"test_object\" has an array field called 'a'"); } TEST_F(RenameNodeTest, MoveIntoArrayNoId) { @@ -291,11 +292,12 @@ TEST_F(RenameNodeTest, MoveIntoArrayNoId) { setPathToCreate("2"); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::BadValue, - "The destination field cannot be an array element, 'a.2' in doc " - "with no id has an array field called 'a'"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "The destination field cannot be an array element, 'a.2' in doc " + "with no id has an array field called 'a'"); } TEST_F(RenameNodeTest, MoveToArrayElement) { @@ -307,11 +309,12 @@ TEST_F(RenameNodeTest, MoveToArrayElement) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: [1, 2], b: 2}")); setPathTaken("a.1"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"][1])), - AssertionException, - ErrorCodes::BadValue, - "The destination field cannot be an array element, 'a.1' in doc " - "with _id: \"test_object\" has an array field called 'a'"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"][1]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::BadValue, + "The destination field cannot be an array element, 'a.1' in doc " + "with _id: \"test_object\" has an array field called 'a'"); } TEST_F(RenameNodeTest, MoveOutOfArray) { @@ -323,7 +326,7 @@ TEST_F(RenameNodeTest, MoveOutOfArray) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: [1, 2]}")); setPathToCreate("b"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "The source field cannot be an array element, 'a.0' in doc with " @@ -340,7 +343,7 @@ TEST_F(RenameNodeTest, MoveNonexistentEmbeddedFieldOut) { setPathToCreate("b"); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::PathNotViable, "cannot use the part (a of a.a) to traverse the element ({a: [ { a: 1 }, { b: 2 } ]})"); @@ -355,7 +358,7 @@ TEST_F(RenameNodeTest, MoveEmbeddedFieldOutWithElementNumber) { mutablebson::Document doc(fromjson("{_id: 'test_object', a: [{a: 1}, {b: 2}]}")); setPathToCreate("b"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "The source field cannot be an array element, 'a.0.a' in doc with " @@ -371,7 +374,7 @@ TEST_F(RenameNodeTest, ReplaceArrayField) { mutablebson::Document doc(fromjson("{a: 2, b: []}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); @@ -388,7 +391,7 @@ TEST_F(RenameNodeTest, ReplaceWithArrayField) { mutablebson::Document doc(fromjson("{a: [], b: 2}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: []}"), doc); @@ -405,7 +408,7 @@ TEST_F(RenameNodeTest, CanRenameFromInvalidFieldName) { mutablebson::Document doc(fromjson("{$a: 2}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -422,7 +425,7 @@ TEST_F(RenameNodeTest, RenameWithoutLogBuilderOrIndexData) { mutablebson::Document doc(fromjson("{a: 2}")); setPathToCreate("b"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{b: 2}"), doc); } @@ -436,7 +439,7 @@ TEST_F(RenameNodeTest, RenameFromNonExistentPathIsNoOp) { mutablebson::Document doc(fromjson("{b: 2}")); setPathTaken("b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["b"])); + auto result = node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 2}"), doc); @@ -452,7 +455,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); setPathToCreate("b"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::InvalidDBRef, "The DBRef $ref field must be followed by a $id field"); @@ -468,7 +471,7 @@ TEST_F(RenameNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFa setPathToCreate("b"); addIndexedPath("a"); setValidateForStorage(false); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); auto updated = BSON("a" << BSON("$ref" @@ -491,7 +494,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemoveImmutablePath) { setPathToCreate("c"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); @@ -507,7 +510,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { setPathToCreate("c"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a' would modify the immutable field 'a.b'"); @@ -523,7 +526,7 @@ TEST_F(RenameNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { setPathToCreate("d"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); @@ -539,7 +542,7 @@ TEST_F(RenameNodeTest, ApplyCanRemoveImmutablePathIfNoop) { setPathToCreate("d"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {}}}"), doc); @@ -557,7 +560,7 @@ TEST_F(RenameNodeTest, ApplyCannotCreateDollarPrefixedField) { mutablebson::Document doc(fromjson("{a: 0}")); setPathToCreate("$bad"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in '$bad' is not valid for storage."); @@ -573,7 +576,7 @@ TEST_F(RenameNodeTest, ApplyCannotOverwriteImmutablePath) { setPathTaken("b"); addImmutablePath("b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["b"])), + node.apply(getApplyParams(doc.root()["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'b' would modify the immutable field 'b'"); diff --git a/src/mongo/db/update/set_node_test.cpp b/src/mongo/db/update/set_node_test.cpp index 56461bb1000..f7280e83110 100644 --- a/src/mongo/db/update/set_node_test.cpp +++ b/src/mongo/db/update/set_node_test.cpp @@ -69,7 +69,7 @@ TEST_F(SetNodeTest, ApplyNoOp) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); @@ -87,7 +87,7 @@ TEST_F(SetNodeTest, ApplyEmptyPathToCreate) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6}"), doc); @@ -106,7 +106,7 @@ TEST_F(SetNodeTest, ApplyCreatePath) { setPathToCreate("b.c"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {d: 5, b: {c: 6}}}"), doc); @@ -124,7 +124,7 @@ TEST_F(SetNodeTest, ApplyCreatePathFromRoot) { mutablebson::Document doc(fromjson("{c: 5}")); setPathToCreate("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{c: 5, a: {b: 6}}"), doc); @@ -143,7 +143,7 @@ TEST_F(SetNodeTest, ApplyPositional) { setPathTaken("a.1"); setMatchedField("1"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][1])); + auto result = node.apply(getApplyParams(doc.root()["a"][1]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, 6, 2]}"), doc); @@ -162,10 +162,11 @@ TEST_F(SetNodeTest, ApplyNonViablePathToCreate) { setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field 'b' in element {a: 5}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field 'b' in element {a: 5}"); } TEST_F(SetNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { @@ -179,7 +180,7 @@ TEST_F(SetNodeTest, ApplyNonViablePathToCreateFromReplicationIsNoOp) { setPathTaken("a"); addIndexedPath("a"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); @@ -197,7 +198,7 @@ TEST_F(SetNodeTest, ApplyNoIndexDataNoLogBuilder) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6}"), doc); @@ -214,7 +215,7 @@ TEST_F(SetNodeTest, ApplyDoesNotAffectIndexes) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 6}"), doc); @@ -231,7 +232,7 @@ TEST_F(SetNodeTest, TypeChangeIsNotANoop) { mutablebson::Document doc(fromjson("{a: NumberInt(2)}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: NumberLong(2)}"), doc); @@ -252,7 +253,7 @@ TEST_F(SetNodeTest, IdentityOpOnDeserializedIsNotANoOp) { setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : NumberInt(2)}}"), doc); @@ -269,7 +270,7 @@ TEST_F(SetNodeTest, ApplyEmptyDocument) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -286,7 +287,7 @@ TEST_F(SetNodeTest, ApplyInPlace) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -303,7 +304,7 @@ TEST_F(SetNodeTest, ApplyOverridePath) { mutablebson::Document doc(fromjson("{a: {b: 1}}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -320,7 +321,7 @@ TEST_F(SetNodeTest, ApplyChangeType) { mutablebson::Document doc(fromjson("{a: 'str'}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -337,7 +338,7 @@ TEST_F(SetNodeTest, ApplyNewPath) { mutablebson::Document doc(fromjson("{b: 1}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, a: 2}"), doc); @@ -353,7 +354,7 @@ TEST_F(SetNodeTest, ApplyLog) { mutablebson::Document doc(fromjson("{a: 1}")); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: 2}"), doc); ASSERT_TRUE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -370,7 +371,7 @@ TEST_F(SetNodeTest, ApplyNoOpDottedPath) { mutablebson::Document doc(fromjson("{a: {b: 2}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : 2}}"), doc); @@ -387,7 +388,7 @@ TEST_F(SetNodeTest, TypeChangeOnDottedPathIsNotANoOp) { mutablebson::Document doc(fromjson("{a: {b: NumberLong(2)}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b : NumberLong(2)}}"), doc); @@ -404,10 +405,11 @@ TEST_F(SetNodeTest, ApplyPathNotViable) { mutablebson::Document doc(fromjson("{a:1}")); setPathToCreate("b"); setPathTaken("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field 'b' in element {a: 1}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field 'b' in element {a: 1}"); } TEST_F(SetNodeTest, ApplyPathNotViableArrray) { @@ -419,10 +421,11 @@ TEST_F(SetNodeTest, ApplyPathNotViableArrray) { mutablebson::Document doc(fromjson("{a:[{b:1}]}")); setPathToCreate("b"); setPathTaken("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field 'b' in element {a: [ { b: 1 } ]}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field 'b' in element {a: [ { b: 1 } ]}"); } TEST_F(SetNodeTest, ApplyInPlaceDottedPath) { @@ -434,7 +437,7 @@ TEST_F(SetNodeTest, ApplyInPlaceDottedPath) { mutablebson::Document doc(fromjson("{a: {b: 1}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -451,7 +454,7 @@ TEST_F(SetNodeTest, ApplyChangeTypeDottedPath) { mutablebson::Document doc(fromjson("{a: {b: 'str'}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -468,7 +471,7 @@ TEST_F(SetNodeTest, ApplyChangePath) { mutablebson::Document doc(fromjson("{a: {b: {c: 1}}}")); setPathTaken("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -486,7 +489,7 @@ TEST_F(SetNodeTest, ApplyExtendPath) { setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {c: 1, b: 2}}"), doc); @@ -503,7 +506,7 @@ TEST_F(SetNodeTest, ApplyNewDottedPath) { mutablebson::Document doc(fromjson("{c: 1}")); setPathToCreate("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{c: 1, a: {b: 2}}"), doc); @@ -520,7 +523,7 @@ TEST_F(SetNodeTest, ApplyEmptyDoc) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -537,7 +540,7 @@ TEST_F(SetNodeTest, ApplyFieldWithDot) { mutablebson::Document doc(fromjson("{'a.b':4}")); setPathToCreate("a.b"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {b: 2}}"), doc); @@ -554,7 +557,7 @@ TEST_F(SetNodeTest, ApplyNoOpArrayIndex) { mutablebson::Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}")); setPathTaken("a.2.b"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); @@ -571,7 +574,7 @@ TEST_F(SetNodeTest, TypeChangeInArrayIsNotANoOp) { mutablebson::Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 2.0}]}")); setPathTaken("a.2.b"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: NumberInt(2)}]}"), doc); @@ -588,10 +591,11 @@ TEST_F(SetNodeTest, ApplyNonViablePath) { mutablebson::Document doc(fromjson("{a: 0}")); setPathToCreate("2.b"); setPathTaken("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field '2' in element {a: 0}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field '2' in element {a: 0}"); } TEST_F(SetNodeTest, ApplyInPlaceArrayIndex) { @@ -603,7 +607,7 @@ TEST_F(SetNodeTest, ApplyInPlaceArrayIndex) { mutablebson::Document doc(fromjson("{a: [{b: 0},{b: 1},{b: 1}]}")); setPathTaken("a.2.b"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][2]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); @@ -621,7 +625,7 @@ TEST_F(SetNodeTest, ApplyNormalArray) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},{b: 1},{b: 2}]}"), doc); @@ -639,7 +643,7 @@ TEST_F(SetNodeTest, ApplyPaddingArray) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0},null,{b: 2}]}"), doc); @@ -657,7 +661,7 @@ TEST_F(SetNodeTest, ApplyNumericObject) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 0, '2': {b: 2}}}"), doc); @@ -674,7 +678,7 @@ TEST_F(SetNodeTest, ApplyNumericField) { mutablebson::Document doc(fromjson("{a: {'2': {b: 1}}}")); setPathTaken("a.2.b"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); @@ -692,7 +696,7 @@ TEST_F(SetNodeTest, ApplyExtendNumericField) { setPathToCreate("b"); setPathTaken("a.2"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"]["2"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["2"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {c: 1, b: 2}}}"), doc); @@ -710,7 +714,7 @@ TEST_F(SetNodeTest, ApplyEmptyObject) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); @@ -728,7 +732,7 @@ TEST_F(SetNodeTest, ApplyEmptyArray) { setPathToCreate("2.b"); setPathTaken("a"); addIndexedPath("a.2.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [null, null, {b: 2}]}"), doc); @@ -745,7 +749,7 @@ TEST_F(SetNodeTest, ApplyLogDottedPath) { mutablebson::Document doc(fromjson("{a: [{b:0}, {b:1}]}")); setPathToCreate("2.b"); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: [{b:0}, {b:1}, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -762,7 +766,7 @@ TEST_F(SetNodeTest, LogEmptyArray) { mutablebson::Document doc(fromjson("{a: []}")); setPathToCreate("2.b"); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: [null, null, {b:2}]}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -779,7 +783,7 @@ TEST_F(SetNodeTest, LogEmptyObject) { mutablebson::Document doc(fromjson("{a: {}}")); setPathToCreate("2.b"); setPathTaken("a"); - node.apply(getApplyParams(doc.root()["a"])); + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_EQUALS(fromjson("{a: {'2': {b: 2}}}"), doc); ASSERT_FALSE(doc.isInPlaceModeEnabled()); ASSERT_EQUALS(countChildren(getLogDoc().root()), 1u); @@ -796,7 +800,7 @@ TEST_F(SetNodeTest, ApplyNoOpComplex) { mutablebson::Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}")); setPathTaken("a.1.b"); addIndexedPath("a.1.b"); - auto result = node.apply(getApplyParams(doc.root()["a"][1]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][1]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"), doc); @@ -813,7 +817,7 @@ TEST_F(SetNodeTest, ApplySameStructure) { mutablebson::Document doc(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, xxx: 1}}]}}")); setPathTaken("a.1.b"); addIndexedPath("a.1.b"); - auto result = node.apply(getApplyParams(doc.root()["a"][1]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][1]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: {c: 0, d: 0}}, {b: {c: 1, d: 1}}]}}"), doc); @@ -830,10 +834,11 @@ TEST_F(SetNodeTest, NonViablePathWithoutRepl) { mutablebson::Document doc(fromjson("{a: 1}")); setPathToCreate("1.b"); setPathTaken("a"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::PathNotViable, - "Cannot create field '1' in element {a: 1}"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::PathNotViable, + "Cannot create field '1' in element {a: 1}"); } TEST_F(SetNodeTest, SingleFieldFromReplication) { @@ -847,7 +852,7 @@ TEST_F(SetNodeTest, SingleFieldFromReplication) { setPathTaken("a"); addIndexedPath("a.1.b"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id:1, a: 1}"), doc); @@ -866,7 +871,7 @@ TEST_F(SetNodeTest, SingleFieldNoIdFromReplication) { setPathTaken("a"); addIndexedPath("a.1.b"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 1}"), doc); @@ -885,7 +890,7 @@ TEST_F(SetNodeTest, NestedFieldFromReplication) { setPathTaken("a.a"); addIndexedPath("a.a.1.b"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"]["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id:1, a: {a: 1}}"), doc); @@ -904,7 +909,7 @@ TEST_F(SetNodeTest, DoubleNestedFieldFromReplication) { setPathTaken("a.b.c"); addIndexedPath("a.b.c.d"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id:1, a: {b: {c: 1}}}"), doc); @@ -923,7 +928,7 @@ TEST_F(SetNodeTest, NestedFieldNoIdFromReplication) { setPathTaken("a.a"); addIndexedPath("a.a.1.b"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"]["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {a: 1}}"), doc); @@ -942,7 +947,7 @@ TEST_F(SetNodeTest, ReplayArrayFieldNotAppendedIntermediateFromReplication) { setPathTaken("a.0"); addIndexedPath("a.1.b"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["a"][0])); + auto result = node.apply(getApplyParams(doc.root()["a"][0]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 0, a: [1, {b: [1]}]}"), doc); @@ -959,7 +964,7 @@ TEST_F(SetNodeTest, Set6) { mutablebson::Document doc(fromjson("{_id: 1, r: {a:1, b:2}}")); setPathTaken("r.a"); addIndexedPath("r.a"); - auto result = node.apply(getApplyParams(doc.root()["r"]["a"])); + auto result = node.apply(getApplyParams(doc.root()["r"]["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2}}"), doc); @@ -979,7 +984,7 @@ TEST_F(SetNodeTest, Set6FromRepl) { setPathTaken("r.a"); addIndexedPath("r.a"); setFromOplogApplication(true); - auto result = node.apply(getApplyParams(doc.root()["r"]["a"])); + auto result = node.apply(getApplyParams(doc.root()["r"]["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{_id: 1, r: {a:2, b:2} }"), doc); @@ -1006,7 +1011,7 @@ TEST_F(SetNodeTest, ApplySetModToEphemeralDocument) { setPathTaken("x"); addIndexedPath("x"); - auto result = node.apply(getApplyParams(doc.root()["x"])); + auto result = node.apply(getApplyParams(doc.root()["x"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{ x : { a : 100, b : 2 } }"), doc); @@ -1022,7 +1027,7 @@ TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldInsideSetElement) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"])), + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); @@ -1037,7 +1042,7 @@ TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldAtStartOfPath) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("$bad.a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in '$bad' is not valid for storage."); @@ -1052,7 +1057,7 @@ TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldInMiddleOfPath) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a.$bad.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); @@ -1067,7 +1072,7 @@ TEST_F(SetNodeTest, ApplyCannotCreateDollarPrefixedFieldAtEndOfPath) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a.$bad"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root())), + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::DollarPrefixedFieldName, "The dollar ($) prefixed field '$bad' in 'a.$bad' is not valid for storage."); @@ -1083,7 +1088,7 @@ TEST_F(SetNodeTest, ApplyCanCreateDollarPrefixedFieldNameWhenValidateForStorageI setPathToCreate("$bad"); addIndexedPath("$bad"); setValidateForStorage(false); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{$bad: 1}"), doc); @@ -1103,7 +1108,7 @@ TEST_F(SetNodeTest, ApplyCannotOverwriteImmutablePath) { setPathTaken("a.b"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"]["b"])), + node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); @@ -1119,7 +1124,7 @@ TEST_F(SetNodeTest, ApplyCanPerformNoopOnImmutablePath) { setPathTaken("a.b"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -1139,7 +1144,7 @@ TEST_F(SetNodeTest, ApplyCannotOverwritePrefixToRemoveImmutablePath) { setPathTaken("a"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"])), + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "After applying the update, the immutable field 'a.b' was found to have been removed."); @@ -1154,11 +1159,12 @@ TEST_F(SetNodeTest, ApplyCannotOverwritePrefixToModifyImmutablePath) { mutablebson::Document doc(fromjson("{a: {b: 2}}")); setPathTaken("a"); addImmutablePath("a.b"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"])), - AssertionException, - ErrorCodes::ImmutableField, - "After applying the update, the immutable field 'a.b' was found to " - "have been altered to b: 1"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::ImmutableField, + "After applying the update, the immutable field 'a.b' was found to " + "have been altered to b: 1"); } TEST_F(SetNodeTest, ApplyCanPerformNoopOnPrefixOfImmutablePath) { @@ -1171,7 +1177,7 @@ TEST_F(SetNodeTest, ApplyCanPerformNoopOnPrefixOfImmutablePath) { setPathTaken("a"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -1191,7 +1197,7 @@ TEST_F(SetNodeTest, ApplyCanOverwritePrefixToCreateImmutablePath) { setPathTaken("a"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -1211,7 +1217,7 @@ TEST_F(SetNodeTest, ApplyCanOverwritePrefixOfImmutablePathIfNoopOnImmutablePath) setPathTaken("a"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2, c: 3}}"), doc); @@ -1231,7 +1237,7 @@ TEST_F(SetNodeTest, ApplyCannotOverwriteSuffixOfImmutablePath) { setPathTaken("a.b.c"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), + node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); @@ -1247,7 +1253,7 @@ TEST_F(SetNodeTest, ApplyCanPerformNoopOnSuffixOfImmutablePath) { setPathTaken("a.b.c"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {c: 2}}}"), doc); @@ -1268,7 +1274,7 @@ TEST_F(SetNodeTest, ApplyCannotCreateFieldAtEndOfImmutablePath) { setPathTaken("a.b"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"]["b"])), + node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Updating the path 'a.b' to b: { c: 1 } would modify the immutable field 'a.b'"); @@ -1285,7 +1291,7 @@ TEST_F(SetNodeTest, ApplyCannotCreateFieldBeyondEndOfImmutablePath) { setPathTaken("a.b"); addImmutablePath("a"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"]["b"])), + node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Updating the path 'a.b' to b: { c: 1 } would modify the immutable field 'a'"); @@ -1302,7 +1308,7 @@ TEST_F(SetNodeTest, ApplyCanCreateImmutablePath) { setPathTaken("a"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 2}}"), doc); @@ -1322,7 +1328,7 @@ TEST_F(SetNodeTest, ApplyCanCreatePrefixOfImmutablePath) { setPathToCreate("a"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -1342,7 +1348,7 @@ TEST_F(SetNodeTest, ApplySetFieldInNonExistentArrayElementAffectsIndexOnSiblingF setPathToCreate("1.c"); setPathTaken("a"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0}, {c: 2}]}"), doc); @@ -1362,7 +1368,7 @@ TEST_F(SetNodeTest, ApplySetFieldInExistingArrayElementDoesNotAffectIndexOnSibli setPathToCreate("c"); setPathTaken("a.0"); addIndexedPath("a.b"); - auto result = node.apply(getApplyParams(doc.root()["a"][0])); + auto result = node.apply(getApplyParams(doc.root()["a"][0]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [{b: 0, c: 2}]}"), doc); @@ -1383,7 +1389,7 @@ TEST_F(SetNodeTest, ApplySetFieldInNonExistentNumericFieldDoesNotAffectIndexOnSi setPathTaken("a"); addIndexedPath("a.b"); addIndexedPath("a.1.b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {'0': {b: 0}, '1': {c: 2}}}"), doc); @@ -1402,7 +1408,7 @@ TEST_F(SetNodeTest, ApplySetOnInsertIsNoopWhenInsertIsFalse) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -1422,7 +1428,7 @@ TEST_F(SetNodeTest, ApplySetOnInsertIsAppliedWhenInsertIsTrue) { setInsert(true); addIndexedPath("a"); setLogBuilderToNull(); // The log builder is null for inserts. - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); @@ -1441,7 +1447,7 @@ TEST_F(SetNodeTest, ApplySetOnInsertExistingPath) { setInsert(true); addIndexedPath("a"); setLogBuilderToNull(); // The log builder is null for inserts. - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 2}"), doc); diff --git a/src/mongo/db/update/unset_node_test.cpp b/src/mongo/db/update/unset_node_test.cpp index dfd3ac52164..346c5e4551c 100644 --- a/src/mongo/db/update/unset_node_test.cpp +++ b/src/mongo/db/update/unset_node_test.cpp @@ -53,14 +53,16 @@ DEATH_TEST(UnsetNodeTest, InitFailsForEmptyElement, "Invariant failure modExpr.o node.init(update["$unset"].embeddedObject().firstElement(), expCtx).transitional_ignore(); } -DEATH_TEST_F(UnsetNodeTest, ApplyToRootFails, "Invariant failure !applyParams.pathTaken->empty()") { +DEATH_TEST_F(UnsetNodeTest, + ApplyToRootFails, + "Invariant failure !updateNodeApplyParams.pathTaken->empty()") { auto update = fromjson("{$unset: {}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); UnsetNode node; ASSERT_OK(node.init(update["$unset"], expCtx)); mutablebson::Document doc(fromjson("{a: 5}")); - node.apply(getApplyParams(doc.root())); + node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); } TEST(UnsetNodeTest, InitSucceedsForNonemptyElement) { @@ -80,7 +82,7 @@ TEST_F(UnsetNodeTest, UnsetNoOp) { mutablebson::Document doc(fromjson("{b: 5}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 5}"), doc); @@ -99,7 +101,7 @@ TEST_F(UnsetNodeTest, UnsetNoOpDottedPath) { setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: 5}"), doc); @@ -118,7 +120,7 @@ TEST_F(UnsetNodeTest, UnsetNoOpThroughArray) { setPathToCreate("b"); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a:[{b:1}]}"), doc); @@ -136,7 +138,7 @@ TEST_F(UnsetNodeTest, UnsetNoOpEmptyDoc) { mutablebson::Document doc(fromjson("{}")); setPathToCreate("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root())); + auto result = node.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -154,7 +156,7 @@ TEST_F(UnsetNodeTest, UnsetTopLevelPath) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -172,7 +174,7 @@ TEST_F(UnsetNodeTest, UnsetNestedPath) { mutablebson::Document doc(fromjson("{a: {b: {c: 6}}}}")); setPathTaken("a.b.c"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: {}}}"), doc); @@ -190,7 +192,7 @@ TEST_F(UnsetNodeTest, UnsetObject) { mutablebson::Document doc(fromjson("{a: {b: {c: 6}}}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {}}"), doc); @@ -208,7 +210,7 @@ TEST_F(UnsetNodeTest, UnsetArrayElement) { mutablebson::Document doc(fromjson("{a:[1], b:1}")); setPathTaken("a.0"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][0])); + auto result = node.apply(getApplyParams(doc.root()["a"][0]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a:[null], b:1}"), doc); @@ -227,7 +229,7 @@ TEST_F(UnsetNodeTest, UnsetPositional) { setPathTaken("a.1"); setMatchedField("1"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][1])); + auto result = node.apply(getApplyParams(doc.root()["a"][1]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: [0, null, 2]}"), doc); @@ -245,7 +247,7 @@ TEST_F(UnsetNodeTest, UnsetEntireArray) { mutablebson::Document doc(fromjson("{a: [0, 1, 2]}")); setPathTaken("a"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -263,7 +265,7 @@ TEST_F(UnsetNodeTest, UnsetFromObjectInArray) { mutablebson::Document doc(fromjson("{a: [{b: 1}]}")); setPathTaken("a.0.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][0]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][0]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a:[{}]}"), doc); @@ -281,7 +283,7 @@ TEST_F(UnsetNodeTest, CanUnsetInvalidField) { mutablebson::Document doc(fromjson("{b: 1, a: [{$b: 1}]}")); setPathTaken("a.0.$b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"][0]["$b"])); + auto result = node.apply(getApplyParams(doc.root()["a"][0]["$b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{b: 1, a: [{}]}"), doc); @@ -299,7 +301,7 @@ TEST_F(UnsetNodeTest, ApplyNoIndexDataNoLogBuilder) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); setLogBuilderToNull(); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -316,7 +318,7 @@ TEST_F(UnsetNodeTest, ApplyDoesNotAffectIndexes) { mutablebson::Document doc(fromjson("{a: 5}")); setPathTaken("a"); addIndexedPath("b"); - auto result = node.apply(getApplyParams(doc.root()["a"])); + auto result = node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{}"), doc); @@ -334,7 +336,7 @@ TEST_F(UnsetNodeTest, ApplyFieldWithDot) { mutablebson::Document doc(fromjson("{'a.b':4, a: {b: 2}}")); setPathTaken("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); ASSERT_EQUALS(fromjson("{'a.b':4, a: {}}"), doc); @@ -351,10 +353,11 @@ TEST_F(UnsetNodeTest, ApplyCannotRemoveRequiredPartOfDBRef) { mutablebson::Document doc(fromjson("{a: {$ref: 'c', $id: 0}}")); setPathTaken("a.$id"); - ASSERT_THROWS_CODE_AND_WHAT(node.apply(getApplyParams(doc.root()["a"]["$id"])), - AssertionException, - ErrorCodes::InvalidDBRef, - "The DBRef $ref field must be followed by a $id field"); + ASSERT_THROWS_CODE_AND_WHAT( + node.apply(getApplyParams(doc.root()["a"]["$id"]), getUpdateNodeApplyParams()), + AssertionException, + ErrorCodes::InvalidDBRef, + "The DBRef $ref field must be followed by a $id field"); } TEST_F(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFalse) { @@ -367,7 +370,7 @@ TEST_F(UnsetNodeTest, ApplyCanRemoveRequiredPartOfDBRefIfValidateForStorageIsFal setPathTaken("a.$id"); addIndexedPath("a"); setValidateForStorage(false); - auto result = node.apply(getApplyParams(doc.root()["a"]["$id"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["$id"]), getUpdateNodeApplyParams()); ASSERT_FALSE(result.noop); ASSERT_TRUE(result.indexesAffected); auto updated = BSON("a" << BSON("$ref" @@ -388,7 +391,7 @@ TEST_F(UnsetNodeTest, ApplyCannotRemoveImmutablePath) { setPathTaken("a.b"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"]["b"])), + node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b' would modify the immutable field 'a.b'"); @@ -404,7 +407,7 @@ TEST_F(UnsetNodeTest, ApplyCannotRemovePrefixOfImmutablePath) { setPathTaken("a"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"])), + node.apply(getApplyParams(doc.root()["a"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a' would modify the immutable field 'a.b'"); @@ -420,7 +423,7 @@ TEST_F(UnsetNodeTest, ApplyCannotRemoveSuffixOfImmutablePath) { setPathTaken("a.b.c"); addImmutablePath("a.b"); ASSERT_THROWS_CODE_AND_WHAT( - node.apply(getApplyParams(doc.root()["a"]["b"]["c"])), + node.apply(getApplyParams(doc.root()["a"]["b"]["c"]), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ImmutableField, "Performing an update on the path 'a.b.c' would modify the immutable field 'a.b'"); @@ -437,7 +440,7 @@ TEST_F(UnsetNodeTest, ApplyCanRemoveImmutablePathIfNoop) { setPathTaken("a.b"); addImmutablePath("a.b"); addIndexedPath("a"); - auto result = node.apply(getApplyParams(doc.root()["a"]["b"])); + auto result = node.apply(getApplyParams(doc.root()["a"]["b"]), getUpdateNodeApplyParams()); ASSERT_TRUE(result.noop); ASSERT_FALSE(result.indexesAffected); ASSERT_EQUALS(fromjson("{a: {b: 1}}"), doc); diff --git a/src/mongo/db/update/update_array_node.cpp b/src/mongo/db/update/update_array_node.cpp index d895272e8f3..ab062760d62 100644 --- a/src/mongo/db/update/update_array_node.cpp +++ b/src/mongo/db/update/update_array_node.cpp @@ -47,13 +47,15 @@ std::unique_ptr<UpdateNode> UpdateArrayNode::createUpdateNodeByMerging( return std::move(mergedNode); } -UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { - if (!applyParams.pathToCreate->empty()) { - for (size_t i = 0; i < applyParams.pathToCreate->numParts(); ++i) { - applyParams.pathTaken->appendPart(applyParams.pathToCreate->getPart(i)); +UpdateExecutor::ApplyResult UpdateArrayNode::apply( + ApplyParams applyParams, UpdateNodeApplyParams updateNodeApplyParams) const { + if (!updateNodeApplyParams.pathToCreate->empty()) { + for (size_t i = 0; i < updateNodeApplyParams.pathToCreate->numParts(); ++i) { + updateNodeApplyParams.pathTaken->appendPart( + updateNodeApplyParams.pathToCreate->getPart(i)); } uasserted(ErrorCodes::BadValue, - str::stream() << "The path '" << applyParams.pathTaken->dottedField() + str::stream() << "The path '" << updateNodeApplyParams.pathTaken->dottedField() << "' must exist in the document in order to apply array updates."); } @@ -116,7 +118,7 @@ UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { // Merge all of the updates for this array element. invariant(updates->second.size() > 0); auto mergedChild = updates->second[0]; - FieldRef::FieldRefTempAppend tempAppend(*(applyParams.pathTaken), + FieldRef::FieldRefTempAppend tempAppend(*(updateNodeApplyParams.pathTaken), childElement.getFieldName()); for (size_t j = 1; j < updates->second.size(); ++j) { @@ -131,7 +133,7 @@ UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { // result. _mergedChildrenCache[mergedChild][updates->second[j]] = UpdateNode::createUpdateNodeByMerging( - *mergedChild, *updates->second[j], applyParams.pathTaken.get()); + *mergedChild, *updates->second[j], updateNodeApplyParams.pathTaken.get()); mergedChild = _mergedChildrenCache[mergedChild][updates->second[j]].get(); } @@ -140,8 +142,10 @@ UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { if (!childrenShouldLogThemselves) { childApplyParams.logBuilder = nullptr; } + auto childUpdateNodeApplyParams = updateNodeApplyParams; - auto childApplyResult = mergedChild->apply(childApplyParams); + auto childApplyResult = + mergedChild->apply(childApplyParams, childUpdateNodeApplyParams); applyResult.indexesAffected = applyResult.indexesAffected || childApplyResult.indexesAffected; @@ -157,7 +161,7 @@ UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { // If no elements match the array filter, report the path to the array itself as modified. if (applyParams.modifiedPaths && matchingElements.size() == 0) { - applyParams.modifiedPaths->keepShortest(*applyParams.pathTaken); + applyParams.modifiedPaths->keepShortest(*updateNodeApplyParams.pathTaken); } // If the child updates have not been logged, log the updated array elements. @@ -166,17 +170,17 @@ UpdateNode::ApplyResult UpdateArrayNode::apply(ApplyParams applyParams) const { // Log the entire array. auto logElement = applyParams.logBuilder->getDocument().makeElementWithNewFieldName( - applyParams.pathTaken->dottedField(), applyParams.element); + updateNodeApplyParams.pathTaken->dottedField(), applyParams.element); invariant(logElement.ok()); uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } else if (nModified == 1) { // Log the modified array element. invariant(modifiedElement); - FieldRef::FieldRefTempAppend tempAppend(*(applyParams.pathTaken), + FieldRef::FieldRefTempAppend tempAppend(*(updateNodeApplyParams.pathTaken), modifiedElement->getFieldName()); auto logElement = applyParams.logBuilder->getDocument().makeElementWithNewFieldName( - applyParams.pathTaken->dottedField(), *modifiedElement); + updateNodeApplyParams.pathTaken->dottedField(), *modifiedElement); invariant(logElement.ok()); uassertStatusOK(applyParams.logBuilder->addToSets(logElement)); } diff --git a/src/mongo/db/update/update_array_node.h b/src/mongo/db/update/update_array_node.h index 2dfad295f62..0c0ec5550d8 100644 --- a/src/mongo/db/update/update_array_node.h +++ b/src/mongo/db/update/update_array_node.h @@ -75,7 +75,8 @@ public: } } - ApplyResult apply(ApplyParams applyParams) const final; + ApplyResult apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const final; UpdateNode* getChild(const std::string& field) const final; diff --git a/src/mongo/db/update/update_array_node_test.cpp b/src/mongo/db/update/update_array_node_test.cpp index 5fb841484f0..ed45c83be63 100644 --- a/src/mongo/db/update/update_array_node_test.cpp +++ b/src/mongo/db/update/update_array_node_test.cpp @@ -67,7 +67,7 @@ TEST_F(UpdateArrayNodeTest, ApplyCreatePathFails) { mutablebson::Document doc(fromjson("{a: {}}")); addIndexedPath("a"); ASSERT_THROWS_CODE_AND_WHAT( - root.apply(getApplyParams(doc.root())), + root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "The path 'a.b' must exist in the document in order to apply array updates."); @@ -91,7 +91,7 @@ TEST_F(UpdateArrayNodeTest, ApplyToNonArrayFails) { mutablebson::Document doc(fromjson("{a: {}}")); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "Cannot apply array updates to non-array element a: {}"); @@ -115,7 +115,7 @@ TEST_F(UpdateArrayNodeTest, UpdateIsAppliedToAllMatchingElements) { mutablebson::Document doc(fromjson("{a: [0, 1, 0]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [2, 1, 2]}"), doc); @@ -146,7 +146,7 @@ DEATH_TEST_F(UpdateArrayNodeTest, ASSERT_OK(doc.root()["a"][1]["c"].setValueInt(1)); ASSERT_OK(doc.root()["a"][2]["c"].setValueInt(0)); addIndexedPath("a"); - root.apply(getApplyParams(doc.root())); + root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); } TEST_F(UpdateArrayNodeTest, UpdateForEmptyIdentifierIsAppliedToAllArrayElements) { @@ -164,7 +164,7 @@ TEST_F(UpdateArrayNodeTest, UpdateForEmptyIdentifierIsAppliedToAllArrayElements) mutablebson::Document doc(fromjson("{a: [0, 0, 0]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [1, 1, 1]}"), doc); @@ -212,7 +212,7 @@ TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElement) { mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0, d: 0}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 1, c: 1, d: 1}]}"), doc); @@ -251,7 +251,7 @@ TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsUsingMergedChildr mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}, {b: 0, c: 0}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 1, c: 1}, {b: 1, c: 1}]}"), doc); @@ -299,7 +299,7 @@ TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementsWithoutMergedChil mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0, d: 1}, {b: 1, c: 0, d: 0}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 2, c: 2, d: 1}, {b: 1, c: 2, d: 2}]}"), doc); @@ -329,7 +329,7 @@ TEST_F(UpdateArrayNodeTest, ApplyMultipleUpdatesToArrayElementWithEmptyIdentifie mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: 1, c: 1}]}"), doc); @@ -374,7 +374,7 @@ TEST_F(UpdateArrayNodeTest, ApplyNestedArrayUpdates) { mutablebson::Document doc(fromjson("{a: [{x: 0, b: [{c: 0, d: 0}]}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{x: 0, b: [{c: 1, d: 1}]}]}"), doc); @@ -413,7 +413,7 @@ TEST_F(UpdateArrayNodeTest, ApplyUpdatesWithMergeConflictToArrayElementFails) { mutablebson::Document doc(fromjson("{a: [0]}")); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0'"); @@ -449,7 +449,7 @@ TEST_F(UpdateArrayNodeTest, ApplyUpdatesWithEmptyIdentifiersWithMergeConflictToA mutablebson::Document doc(fromjson("{a: [{b: [0]}]}")); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0.b.0'"); @@ -491,7 +491,7 @@ TEST_F(UpdateArrayNodeTest, ApplyNestedArrayUpdatesWithMergeConflictFails) { mutablebson::Document doc(fromjson("{a: [{b: [0], c: 0}]}")); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0.b.0'"); @@ -515,7 +515,7 @@ TEST_F(UpdateArrayNodeTest, NoArrayElementsMatch) { mutablebson::Document doc(fromjson("{a: [2, 2, 2]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: [2, 2, 2]}"), doc); @@ -542,7 +542,7 @@ TEST_F(UpdateArrayNodeTest, UpdatesToAllArrayElementsAreNoops) { mutablebson::Document doc(fromjson("{a: [1, 1, 1]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: [1, 1, 1]}"), doc); @@ -569,7 +569,7 @@ TEST_F(UpdateArrayNodeTest, NoArrayElementAffectsIndexes) { mutablebson::Document doc(fromjson("{a: [{c: 0}, {c: 0}, {c: 0}]}")); addIndexedPath("a.c"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{c: 0, b: 0}, {c: 0, b: 0}, {c: 0, b: 0}]}"), doc); @@ -596,7 +596,7 @@ TEST_F(UpdateArrayNodeTest, WhenOneElementIsMatchedLogElementUpdateDirectly) { mutablebson::Document doc(fromjson("{a: [{c: 1}, {c: 0}, {c: 1}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{c: 1}, {c: 0, b: 0}, {c: 1}]}"), doc); @@ -623,7 +623,7 @@ TEST_F(UpdateArrayNodeTest, WhenOneElementIsModifiedLogElement) { mutablebson::Document doc(fromjson("{a: [{c: 0, b: 0}, {c: 0}, {c: 1}]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{c: 0, b: 0}, {c: 0, b: 0}, {c: 1}]}"), doc); @@ -647,7 +647,7 @@ TEST_F(UpdateArrayNodeTest, ArrayUpdateOnEmptyArrayIsANoop) { mutablebson::Document doc(fromjson("{a: []}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: []}"), doc); @@ -675,7 +675,7 @@ TEST_F(UpdateArrayNodeTest, ApplyPositionalInsideArrayUpdate) { mutablebson::Document doc(fromjson("{a: [{b: [0, 0], c: 0}]}")); addIndexedPath("a"); setMatchedField("1"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [{b: [0, 1], c: 0}]}"), doc); @@ -703,7 +703,7 @@ TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateFromReplication) { mutablebson::Document doc(fromjson("{a: [0]}")); addIndexedPath("a"); setFromOplogApplication(true); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_TRUE(result.noop); ASSERT_EQUALS(fromjson("{a: [0]}"), doc); @@ -730,7 +730,7 @@ TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateNotFromReplication) { mutablebson::Document doc(fromjson("{a: [0]}")); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {0: 0}"); @@ -754,7 +754,7 @@ TEST_F(UpdateArrayNodeTest, ApplyArrayUpdateWithoutLogBuilderOrIndexData) { mutablebson::Document doc(fromjson("{a: [0]}")); setLogBuilderToNull(); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: [1]}"), doc); diff --git a/src/mongo/db/update/update_driver.cpp b/src/mongo/db/update/update_driver.cpp index b071c6e141a..d50a3c9f26f 100644 --- a/src/mongo/db/update/update_driver.cpp +++ b/src/mongo/db/update/update_driver.cpp @@ -74,9 +74,13 @@ StatusWith<UpdateSemantics> updateSemanticsFromElement(BSONElement element) { modifiertable::ModifierType validateMod(BSONElement mod) { auto modType = modifiertable::getType(mod.fieldName()); - uassert(ErrorCodes::FailedToParse, - str::stream() << "Unknown modifier: " << mod.fieldName(), - modType != modifiertable::MOD_UNKNOWN); + uassert( + ErrorCodes::FailedToParse, + str::stream() + << "Unknown modifier: " + << mod.fieldName() + << ". Expected a valid update modifier or pipeline-style update specified as an array", + modType != modifiertable::MOD_UNKNOWN); uassert(ErrorCodes::FailedToParse, str::stream() << "Modifiers operate on fields but we found type " @@ -145,30 +149,39 @@ UpdateDriver::UpdateDriver(const boost::intrusive_ptr<ExpressionContext>& expCtx : _expCtx(expCtx) {} void UpdateDriver::parse( - const BSONObj& updateExpr, + const write_ops::UpdateModification& updateMod, const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>& arrayFilters, const bool multi) { - invariant(!_root && !_replacementMode, "Multiple calls to parse() on same UpdateDriver"); + invariant(!_updateExecutor, "Multiple calls to parse() on same UpdateDriver"); + + if (updateMod.type() == write_ops::UpdateModification::Type::kPipeline) { + uassert(ErrorCodes::FailedToParse, + "arrayFilters may not be specified for pipeline-syle updates", + arrayFilters.empty()); + _updateExecutor = + stdx::make_unique<PipelineExecutor>(_expCtx, updateMod.getUpdatePipeline()); + _updateType = UpdateType::kPipeline; + return; + } // Check if the update expression is a full object replacement. - if (isDocReplacement(updateExpr)) { + if (isDocReplacement(updateMod)) { uassert(ErrorCodes::FailedToParse, "multi update only works with $ operators", !multi); - _root = stdx::make_unique<ObjectReplaceNode>(updateExpr); + _updateExecutor = stdx::make_unique<ObjectReplaceNode>(updateMod.getUpdateClassic()); // Register the fact that this driver will only do full object replacements. - _replacementMode = true; - + _updateType = UpdateType::kReplacement; return; } - // Register the fact that this driver is not doing a full object replacement. - _replacementMode = false; + invariant(_updateType == UpdateType::kOperator); // 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. + auto updateExpr = updateMod.getUpdateClassic(); BSONElement updateSemanticsElement = updateExpr[LogBuilder::kUpdateSemanticsFieldName]; if (updateSemanticsElement) { uassert(ErrorCodes::FailedToParse, @@ -180,7 +193,7 @@ void UpdateDriver::parse( auto root = stdx::make_unique<UpdateObjectNode>(); _positional = parseUpdateExpression(updateExpr, root.get(), _expCtx, arrayFilters); - _root = std::move(root); + _updateExecutor = std::move(root); } Status UpdateDriver::populateDocumentWithQueryFields(OperationContext* opCtx, @@ -216,13 +229,12 @@ Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery& query EqualityMatches equalities; Status status = Status::OK(); - if (isDocReplacement()) { - - // Extract only immutable fields from replacement-style + if (_updateType == UpdateType::kReplacement) { + // Extract only immutable fields. status = pathsupport::extractFullEqualityMatches(*query.root(), immutablePaths, &equalities); } else { - // Extract all fields from op-style + // Extract all fields from op-style update. status = pathsupport::extractEqualityMatches(*query.root(), &equalities); } @@ -243,7 +255,9 @@ Status UpdateDriver::update(StringData matchedField, FieldRefSetWithStorage* modifiedPaths) { // TODO: assert that update() is called at most once in a !_multi case. - _affectIndices = (isDocReplacement() && (_indexedFields != NULL)); + _affectIndices = + ((_updateType == UpdateType::kReplacement || _updateType == UpdateType::kPipeline) && + (_indexedFields != NULL)); _logDoc.reset(); LogBuilder logBuilder(_logDoc.root()); @@ -261,7 +275,9 @@ Status UpdateDriver::update(StringData matchedField, if (_logOp && logOpRec) { applyParams.logBuilder = &logBuilder; } - auto applyResult = _root->apply(applyParams); + + invariant(_updateExecutor); + auto applyResult = _updateExecutor->applyUpdate(applyParams); if (applyResult.indexesAffected) { _affectIndices = true; doc->disableInPlaceUpdates(); @@ -269,7 +285,7 @@ Status UpdateDriver::update(StringData matchedField, if (docWasModified) { *docWasModified = !applyResult.noop; } - if (!_replacementMode && _logOp && logOpRec) { + if (_updateType == UpdateType::kOperator && _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. @@ -289,44 +305,18 @@ Status UpdateDriver::update(StringData matchedField, return Status::OK(); } -bool UpdateDriver::isDocReplacement() const { - return _replacementMode; -} - -bool UpdateDriver::modsAffectIndices() const { - return _affectIndices; -} - -void UpdateDriver::refreshIndexKeys(const UpdateIndexData* indexedFields) { - _indexedFields = indexedFields; -} - -bool UpdateDriver::logOp() const { - return _logOp; -} - -void UpdateDriver::setLogOp(bool logOp) { - _logOp = logOp; -} - -bool UpdateDriver::fromOplogApplication() const { - return _fromOplogApplication; -} - -void UpdateDriver::setFromOplogApplication(bool fromOplogApplication) { - _fromOplogApplication = fromOplogApplication; -} - void UpdateDriver::setCollator(const CollatorInterface* collator) { - if (_root) { - _root->setCollator(collator); + if (_updateExecutor) { + _updateExecutor->setCollator(collator); } _expCtx->setCollator(collator); } -bool UpdateDriver::isDocReplacement(const BSONObj& updateExpr) { - return *updateExpr.firstElementFieldName() != '$'; +bool UpdateDriver::isDocReplacement(const write_ops::UpdateModification& updateMod) { + return (updateMod.type() == write_ops::UpdateModification::Type::kClassic && + *updateMod.getUpdateClassic().firstElementFieldName() != '$') || + updateMod.type() == write_ops::UpdateModification::Type::kPipeline; } } // namespace mongo diff --git a/src/mongo/db/update/update_driver.h b/src/mongo/db/update/update_driver.h index 50469e056cd..4f400ce640c 100644 --- a/src/mongo/db/update/update_driver.h +++ b/src/mongo/db/update/update_driver.h @@ -37,9 +37,13 @@ #include "mongo/bson/mutable/document.h" #include "mongo/db/field_ref_set.h" #include "mongo/db/jsobj.h" +#include "mongo/db/ops/write_ops_parsers.h" +#include "mongo/db/pipeline/pipeline.h" +#include "mongo/db/pipeline/value.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/update/modifier_table.h" #include "mongo/db/update/object_replace_node.h" +#include "mongo/db/update/pipeline_executor.h" #include "mongo/db/update/update_node_visitor.h" #include "mongo/db/update/update_object_node.h" #include "mongo/db/update_index_data.h" @@ -51,13 +55,15 @@ class OperationContext; class UpdateDriver { public: + enum class UpdateType { kOperator, kReplacement, kPipeline }; + UpdateDriver(const boost::intrusive_ptr<ExpressionContext>& expCtx); /** - * Parses the 'updateExpr' update expression into the '_root' member variable. Uasserts - * if 'updateExpr' fails to parse. + * Parses the 'updateExpr' update expression into the '_updateExecutor' member variable. + * Uasserts if 'updateExpr' fails to parse. */ - void parse(const BSONObj& updateExpr, + void parse(const write_ops::UpdateModification& updateExpr, const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>& arrayFilters, const bool multi = false); @@ -121,24 +127,39 @@ public: * implementing methods that operate on the nodes of the tree. */ void visitRoot(UpdateNodeVisitor* visitor) { - _root->acceptVisitor(visitor); + _updateExecutor->acceptVisitor(visitor); } // // Accessors // - bool isDocReplacement() const; - static bool isDocReplacement(const BSONObj& updateExpr); + UpdateType type() const { + return _updateType; + } - bool modsAffectIndices() const; - void refreshIndexKeys(const UpdateIndexData* indexedFields); + static bool isDocReplacement(const write_ops::UpdateModification& updateMod); - bool logOp() const; - void setLogOp(bool logOp); + bool modsAffectIndices() const { + return _affectIndices; + } + void refreshIndexKeys(const UpdateIndexData* indexedFields) { + _indexedFields = indexedFields; + } - bool fromOplogApplication() const; - void setFromOplogApplication(bool fromOplogApplication); + bool logOp() const { + return _logOp; + } + void setLogOp(bool logOp) { + _logOp = logOp; + } + + bool fromOplogApplication() const { + return _fromOplogApplication; + } + void setFromOplogApplication(bool fromOplogApplication) { + _fromOplogApplication = fromOplogApplication; + } mutablebson::Document& getDocument() { return _objDoc; @@ -153,12 +174,20 @@ public: } /** - * Serialize the update expression to BSON. Output of this method is expected to, when parsed, + * Serialize the update expression to Value. Output of this method is expected to, when parsed, * produce a logically equivalent update expression. */ - BSONObj serialize() const { - return _replacementMode ? static_cast<ObjectReplaceNode*>(_root.get())->serialize() - : static_cast<UpdateObjectNode*>(_root.get())->serialize(); + Value serialize() const { + switch (_updateType) { + case UpdateType::kReplacement: + return Value(static_cast<ObjectReplaceNode*>(_updateExecutor.get())->serialize()); + case UpdateType::kPipeline: + return static_cast<PipelineExecutor*>(_updateExecutor.get())->serialize(); + case UpdateType::kOperator: + return Value(static_cast<UpdateObjectNode*>(_updateExecutor.get())->serialize()); + default: + MONGO_UNREACHABLE; + } } /** @@ -176,12 +205,9 @@ private: // immutable properties after parsing // - // Is this a full object replacement or do we have update modifiers in the '_root' UpdateNode - // tree? - bool _replacementMode = false; + UpdateType _updateType = UpdateType::kOperator; - // The root of the UpdateNode tree. - std::unique_ptr<UpdateNode> _root; + std::unique_ptr<UpdateExecutor> _updateExecutor; // What are the list of fields in the collection over which the update is going to be // applied that participate in indices? diff --git a/src/mongo/db/update/update_driver_test.cpp b/src/mongo/db/update/update_driver_test.cpp index 15e4eca2340..5cb065b528f 100644 --- a/src/mongo/db/update/update_driver_test.cpp +++ b/src/mongo/db/update/update_driver_test.cpp @@ -38,6 +38,7 @@ #include "mongo/bson/bsonelement_comparator.h" #include "mongo/bson/mutable/document.h" #include "mongo/bson/mutable/mutable_bson_test_utils.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/field_ref.h" #include "mongo/db/json.h" #include "mongo/db/pipeline/expression_context_for_test.h" @@ -66,7 +67,7 @@ TEST(Parse, Normal) { UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW(driver.parse(fromjson("{$set:{a:1}}"), arrayFilters)); - ASSERT_FALSE(driver.isDocReplacement()); + ASSERT_FALSE(driver.type() == UpdateDriver::UpdateType::kReplacement); } TEST(Parse, MultiMods) { @@ -74,7 +75,7 @@ TEST(Parse, MultiMods) { UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW(driver.parse(fromjson("{$set:{a:1, b:1}}"), arrayFilters)); - ASSERT_FALSE(driver.isDocReplacement()); + ASSERT_FALSE(driver.type() == UpdateDriver::UpdateType::kReplacement); } TEST(Parse, MixingMods) { @@ -82,7 +83,7 @@ TEST(Parse, MixingMods) { UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW(driver.parse(fromjson("{$set:{a:1}, $unset:{b:1}}"), arrayFilters)); - ASSERT_FALSE(driver.isDocReplacement()); + ASSERT_FALSE(driver.type() == UpdateDriver::UpdateType::kReplacement); } TEST(Parse, ObjectReplacment) { @@ -90,7 +91,17 @@ TEST(Parse, ObjectReplacment) { UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW(driver.parse(fromjson("{obj: \"obj replacement\"}"), arrayFilters)); - ASSERT_TRUE(driver.isDocReplacement()); + ASSERT_TRUE(driver.type() == UpdateDriver::UpdateType::kReplacement); +} + +TEST(Parse, ParseUpdateWithPipeline) { + setTestCommandsEnabled(true); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + UpdateDriver driver(expCtx); + std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; + auto updateObj = BSON("u" << BSON_ARRAY(BSON("$addFields" << BSON("a" << 1)))); + ASSERT_DOES_NOT_THROW(driver.parse(updateObj["u"], arrayFilters)); + ASSERT_TRUE(driver.type() == UpdateDriver::UpdateType::kPipeline); } TEST(Parse, EmptyMod) { @@ -111,7 +122,8 @@ TEST(Parse, WrongMod) { ASSERT_THROWS_CODE_AND_WHAT(driver.parse(fromjson("{$xyz:{a:1}}"), arrayFilters), AssertionException, ErrorCodes::FailedToParse, - "Unknown modifier: $xyz"); + "Unknown modifier: $xyz. Expected a valid update modifier or " + "pipeline-style update specified as an array"); } TEST(Parse, WrongType) { @@ -133,7 +145,8 @@ TEST(Parse, ModsWithLaterObjReplacement) { driver.parse(fromjson("{$set:{a:1}, obj: \"obj replacement\"}"), arrayFilters), AssertionException, ErrorCodes::FailedToParse, - "Unknown modifier: obj"); + "Unknown modifier: obj. Expected a valid update modifier or pipeline-style update " + "specified as an array"); } TEST(Parse, SetOnInsert) { @@ -141,7 +154,7 @@ TEST(Parse, SetOnInsert) { UpdateDriver driver(expCtx); std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW(driver.parse(fromjson("{$setOnInsert:{a:1}}"), arrayFilters)); - ASSERT_FALSE(driver.isDocReplacement()); + ASSERT_FALSE(driver.type() == UpdateDriver::UpdateType::kReplacement); } TEST(Collator, SetCollationUpdatesModifierInterfaces) { diff --git a/src/mongo/db/update/update_executor.h b/src/mongo/db/update/update_executor.h new file mode 100644 index 00000000000..0a3baf8779f --- /dev/null +++ b/src/mongo/db/update/update_executor.h @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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/bson/mutable/element.h" +#include "mongo/db/field_ref_set.h" +#include "mongo/db/update/log_builder.h" +#include "mongo/db/update/update_node_visitor.h" +#include "mongo/db/update_index_data.h" + +namespace mongo { + +class CollatorInterface; +class FieldRef; + +/** + * Provides an interface for applying an update to a document. + */ +class UpdateExecutor { +public: + /** + * The parameters required by UpdateExecutor::applyUpdate. + */ + struct ApplyParams { + ApplyParams(mutablebson::Element element, const FieldRefSet& immutablePaths) + : element(element), immutablePaths(immutablePaths) {} + + // The element to update. + mutablebson::Element element; + + // 'applyUpdate' will uassert if it modifies an immutable path. + const FieldRefSet& immutablePaths; + + // If there was a positional ($) element in the update expression, 'matchedField' is the + // index of the array element that caused the query to match the document. + StringData matchedField; + + // True if the update is being applied to a document to be inserted. + bool insert = false; + + // This is provided because some modifiers may ignore certain errors when the update is from + // replication. + bool fromOplogApplication = false; + + // If true, UpdateNode::apply ensures that modified elements do not violate depth or DBRef + // constraints. + bool validateForStorage = true; + + // Used to determine whether indexes are affected. + const UpdateIndexData* indexData = nullptr; + + // If provided, UpdateNode::apply will log the update here. + LogBuilder* logBuilder = nullptr; + + // If provided, UpdateNode::apply will populate this with a path to each modified field. + FieldRefSetWithStorage* modifiedPaths = nullptr; + }; + + /** + * The outputs of apply(). + */ + struct ApplyResult { + static ApplyResult noopResult() { + ApplyResult applyResult; + applyResult.indexesAffected = false; + applyResult.noop = true; + return applyResult; + } + + bool indexesAffected = true; + bool noop = false; + }; + + + UpdateExecutor() = default; + virtual ~UpdateExecutor() = default; + + /** + * Set the collation. This is a noop if the UpdateExecutor subclass does not require a collator. + * If setCollator() is called, it is required that the current collator is the simple collator + * (nullptr). The collator must outlive the modifier interface. This is used to override the + * collation after obtaining a collection lock if the update did not specify a collation and the + * collection has a non-simple default collation. + */ + virtual void setCollator(const CollatorInterface* collator) = 0; + + + /** + * Applies the update to 'applyParams.element'. Returns an ApplyResult specifying whether the + * operation was a no-op and whether indexes are affected. + */ + virtual ApplyResult applyUpdate(ApplyParams applyParams) const = 0; + + /** + * This allows an arbitrary class to implement logic which gets dispatched to at runtime + * depending on the type of the UpdateExecutor. + */ + virtual void acceptVisitor(UpdateNodeVisitor* visitor) = 0; +}; + +} // namespace mongo diff --git a/src/mongo/db/update/update_node.h b/src/mongo/db/update/update_node.h index be4a1b09155..9b3bee195ae 100644 --- a/src/mongo/db/update/update_node.h +++ b/src/mongo/db/update/update_node.h @@ -39,6 +39,7 @@ #include "mongo/bson/mutable/element.h" #include "mongo/db/field_ref_set.h" #include "mongo/db/update/log_builder.h" +#include "mongo/db/update/update_executor.h" #include "mongo/db/update/update_node_visitor.h" #include "mongo/db/update_index_data.h" #include "mongo/util/assert_util.h" @@ -63,39 +64,12 @@ class FieldRef; * b / \ c * SetNode: _val = 5 IncNode: _val = 1 */ -class UpdateNode { +class UpdateNode : public UpdateExecutor { public: enum class Context { kAll, kInsertOnly }; enum class Type { Object, Array, Leaf, Replacement }; - explicit UpdateNode(Type type, Context context = Context::kAll) - : context(context), type(type) {} - virtual ~UpdateNode() = default; - - virtual std::unique_ptr<UpdateNode> clone() const = 0; - - /** - * Set the collation on the node and all descendants. This is a noop if no leaf nodes require a - * collator. If setCollator() is called, it is required that the current collator of all leaf - * nodes is the simple collator (nullptr). The collator must outlive the modifier interface. - * This is used to override the collation after obtaining a collection lock if the update did - * not specify a collation and the collection has a non-simple default collation. - */ - virtual void setCollator(const CollatorInterface* collator) = 0; - - /** - * The parameters required by UpdateNode::apply. - */ - struct ApplyParams { - ApplyParams(mutablebson::Element element, const FieldRefSet& immutablePaths) - : element(element), immutablePaths(immutablePaths) {} - - // The element to update. - mutablebson::Element element; - - // UpdateNode::apply uasserts if it modifies an immutable path. - const FieldRefSet& immutablePaths; - + struct UpdateNodeApplyParams { // The path taken through the UpdateNode tree beyond where the path existed in the document. // For example, if the update is {$set: {'a.b.c': 5}}, and the document is {a: {}}, then at // the leaf node, 'pathToCreate'="b.c". @@ -105,55 +79,21 @@ public: // For example, if the update is {$set: {'a.b.c': 5}}, and the document is {a: {}}, then at // the leaf node, 'pathTaken'="a". std::shared_ptr<FieldRef> pathTaken = std::make_shared<FieldRef>(); + }; - // If there was a positional ($) element in the update expression, 'matchedField' is the - // index of the array element that caused the query to match the document. - StringData matchedField; - - // True if the update is being applied to a document to be inserted. $setOnInsert behaves as - // a no-op when this flag is false. - bool insert = false; - - // This is provided because some modifiers may ignore certain errors when the update is from - // replication. - bool fromOplogApplication = false; - - // If true, UpdateNode::apply ensures that modified elements do not violate depth or DBRef - // constraints. - bool validateForStorage = true; - - // Used to determine whether indexes are affected. - const UpdateIndexData* indexData = nullptr; - - // If provided, UpdateNode::apply will log the update here. - LogBuilder* logBuilder = nullptr; + explicit UpdateNode(Type type, Context context = Context::kAll) + : context(context), type(type) {} + virtual ~UpdateNode() = default; - // If provided, UpdateNode::apply will populate this with a path to each modified field. - FieldRefSetWithStorage* modifiedPaths = nullptr; - }; + virtual std::unique_ptr<UpdateNode> clone() const = 0; - /** - * The outputs of apply(). - */ - struct ApplyResult { - static ApplyResult noopResult() { - ApplyResult applyResult; - applyResult.indexesAffected = false; - applyResult.noop = true; - return applyResult; - } - - bool indexesAffected = true; - bool noop = false; - }; + ApplyResult applyUpdate(ApplyParams applyParams) const final { + UpdateNodeApplyParams updateNodeApplyParams; + return apply(applyParams, updateNodeApplyParams); + } - /** - * Applies the update node to 'applyParams.element', creating the fields in - * 'applyParams.pathToCreate' if required by the leaves (i.e. the leaves are not all $unset). - * Returns an ApplyResult specifying whether the operation was a no-op and whether indexes are - * affected. - */ - virtual ApplyResult apply(ApplyParams applyParams) const = 0; + virtual ApplyResult apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const = 0; /** * Creates a new node by merging the contents of two input nodes. The semantics of the merge @@ -180,12 +120,6 @@ public: std::map<std::string, std::vector<std::pair<std::string, BSONObj>>>* operatorOrientedUpdates) const = 0; - /** - * This allows an arbitrary class to implement logic which gets dispatched to at runtime - * depending on the type of the UpdateNode. - */ - virtual void acceptVisitor(UpdateNodeVisitor* visitor) = 0; - public: const Context context; const Type type; diff --git a/src/mongo/db/update/update_node_test_fixture.h b/src/mongo/db/update/update_node_test_fixture.h index 40ac65f6a75..2f3506ea487 100644 --- a/src/mongo/db/update/update_node_test_fixture.h +++ b/src/mongo/db/update/update_node_test_fixture.h @@ -66,10 +66,8 @@ protected: _modifiedPaths.clear(); } - UpdateNode::ApplyParams getApplyParams(mutablebson::Element element) { - UpdateNode::ApplyParams applyParams(element, _immutablePaths); - applyParams.pathToCreate = _pathToCreate; - applyParams.pathTaken = _pathTaken; + UpdateExecutor::ApplyParams getApplyParams(mutablebson::Element element) { + UpdateExecutor::ApplyParams applyParams(element, _immutablePaths); applyParams.matchedField = _matchedField; applyParams.insert = _insert; applyParams.fromOplogApplication = _fromOplogApplication; @@ -80,6 +78,13 @@ protected: return applyParams; } + UpdateNode::UpdateNodeApplyParams getUpdateNodeApplyParams() { + UpdateNode::UpdateNodeApplyParams applyParams; + applyParams.pathToCreate = _pathToCreate; + applyParams.pathTaken = _pathTaken; + return applyParams; + } + void addImmutablePath(StringData path) { auto fieldRef = stdx::make_unique<FieldRef>(path); _immutablePathsVector.push_back(std::move(fieldRef)); diff --git a/src/mongo/db/update/update_node_visitor.h b/src/mongo/db/update/update_node_visitor.h index 886ce6e112f..55ede3a0e64 100644 --- a/src/mongo/db/update/update_node_visitor.h +++ b/src/mongo/db/update/update_node_visitor.h @@ -40,6 +40,7 @@ class CompareNode; class ConflictPlaceholderNode; class CurrentDateNode; class ObjectReplaceNode; +class PipelineExecutor; class PopNode; class PullAllNode; class PullNode; @@ -72,6 +73,7 @@ public: virtual void visit(ConflictPlaceholderNode*) = 0; virtual void visit(CurrentDateNode*) = 0; virtual void visit(ObjectReplaceNode*) = 0; + virtual void visit(PipelineExecutor*) = 0; virtual void visit(PopNode*) = 0; virtual void visit(PullAllNode*) = 0; virtual void visit(PullNode*) = 0; diff --git a/src/mongo/db/update/update_object_node.cpp b/src/mongo/db/update/update_object_node.cpp index fcde34ac023..e71f273bdff 100644 --- a/src/mongo/db/update/update_object_node.cpp +++ b/src/mongo/db/update/update_object_node.cpp @@ -106,16 +106,17 @@ mutablebson::Element getChild(mutablebson::Element element, StringData field) { */ void applyChild(const UpdateNode& child, StringData field, - UpdateNode::ApplyParams* applyParams, - UpdateNode::ApplyResult* applyResult) { + UpdateExecutor::ApplyParams* applyParams, + UpdateNode::UpdateNodeApplyParams* updateNodeApplyParams, + UpdateExecutor::ApplyResult* applyResult) { - auto pathTakenSizeBefore = applyParams->pathTaken->numParts(); + auto pathTakenSizeBefore = updateNodeApplyParams->pathTaken->numParts(); // A non-ok value for childElement will indicate that we need to append 'field' to the // 'pathToCreate' FieldRef. auto childElement = applyParams->element.getDocument().end(); invariant(!childElement.ok()); - if (!applyParams->pathToCreate->empty()) { + if (!updateNodeApplyParams->pathToCreate->empty()) { // We're already traversing a path with elements that don't exist yet, so we will definitely // need to append. } else { @@ -126,7 +127,7 @@ void applyChild(const UpdateNode& child, // The path we've traversed so far already exists in our document, and 'childElement' // represents the Element indicated by the 'field' name or index, which we indicate by // updating the 'pathTaken' FieldRef. - applyParams->pathTaken->appendPart(field); + updateNodeApplyParams->pathTaken->appendPart(field); } else { // We are traversing path components that do not exist in our document. Any update modifier // that creates new path components (i.e., any modifiers that return true for @@ -134,50 +135,54 @@ void applyChild(const UpdateNode& child, // 'pathToCreate' FieldRef. If the component cannot be created, pathsupport::createPathAt() // will provide a sensible PathNotViable UserError. childElement = applyParams->element; - applyParams->pathToCreate->appendPart(field); + updateNodeApplyParams->pathToCreate->appendPart(field); } auto childApplyParams = *applyParams; childApplyParams.element = childElement; - auto childApplyResult = child.apply(childApplyParams); + UpdateNode::UpdateNodeApplyParams childUpdateNodeApplyParams = *updateNodeApplyParams; + auto childApplyResult = child.apply(childApplyParams, childUpdateNodeApplyParams); applyResult->indexesAffected = applyResult->indexesAffected || childApplyResult.indexesAffected; applyResult->noop = applyResult->noop && childApplyResult.noop; // Pop 'field' off of 'pathToCreate' or 'pathTaken'. - if (!applyParams->pathToCreate->empty()) { - applyParams->pathToCreate->removeLastPart(); + if (!updateNodeApplyParams->pathToCreate->empty()) { + updateNodeApplyParams->pathToCreate->removeLastPart(); } else { - applyParams->pathTaken->removeLastPart(); + updateNodeApplyParams->pathTaken->removeLastPart(); } // If the child is an internal node, it may have created 'pathToCreate' and moved 'pathToCreate' // to the end of 'pathTaken'. We should advance 'element' to the end of 'pathTaken'. - if (applyParams->pathTaken->numParts() > pathTakenSizeBefore) { - for (auto i = pathTakenSizeBefore; i < applyParams->pathTaken->numParts(); ++i) { + if (updateNodeApplyParams->pathTaken->numParts() > pathTakenSizeBefore) { + for (auto i = pathTakenSizeBefore; i < updateNodeApplyParams->pathTaken->numParts(); ++i) { applyParams->element = - getChild(applyParams->element, applyParams->pathTaken->getPart(i)); + getChild(applyParams->element, updateNodeApplyParams->pathTaken->getPart(i)); invariant(applyParams->element.ok()); } - } else if (!applyParams->pathToCreate->empty()) { + } else if (!updateNodeApplyParams->pathToCreate->empty()) { // If the child is a leaf node, it may have created 'pathToCreate' without moving // 'pathToCreate' to the end of 'pathTaken'. We should move 'pathToCreate' to the end of // 'pathTaken' and advance 'element' to the end of 'pathTaken'. - childElement = getChild(applyParams->element, applyParams->pathToCreate->getPart(0)); + childElement = + getChild(applyParams->element, updateNodeApplyParams->pathToCreate->getPart(0)); if (childElement.ok()) { applyParams->element = childElement; - applyParams->pathTaken->appendPart(applyParams->pathToCreate->getPart(0)); + updateNodeApplyParams->pathTaken->appendPart( + updateNodeApplyParams->pathToCreate->getPart(0)); // Either the path was fully created or not created at all. - for (size_t i = 1; i < applyParams->pathToCreate->numParts(); ++i) { + for (size_t i = 1; i < updateNodeApplyParams->pathToCreate->numParts(); ++i) { applyParams->element = - getChild(applyParams->element, applyParams->pathToCreate->getPart(i)); + getChild(applyParams->element, updateNodeApplyParams->pathToCreate->getPart(i)); invariant(applyParams->element.ok()); - applyParams->pathTaken->appendPart(applyParams->pathToCreate->getPart(i)); + updateNodeApplyParams->pathTaken->appendPart( + updateNodeApplyParams->pathToCreate->getPart(i)); } - applyParams->pathToCreate->clear(); + updateNodeApplyParams->pathToCreate->clear(); } } } @@ -394,7 +399,8 @@ BSONObj UpdateObjectNode::serialize() const { return bob.obj(); } -UpdateNode::ApplyResult UpdateObjectNode::apply(ApplyParams applyParams) const { +UpdateExecutor::ApplyResult UpdateObjectNode::apply( + ApplyParams applyParams, UpdateNodeApplyParams updateNodeApplyParams) const { bool applyPositional = _positionalChild.get(); if (applyPositional) { uassert(ErrorCodes::BadValue, @@ -415,22 +421,27 @@ UpdateNode::ApplyResult UpdateObjectNode::apply(ApplyParams applyParams) const { if (mergedChild == _mergedChildrenCache.end()) { // The full path to the merged field is required for error reporting. - for (size_t i = 0; i < applyParams.pathToCreate->numParts(); ++i) { - applyParams.pathTaken->appendPart(applyParams.pathToCreate->getPart(i)); + for (size_t i = 0; i < updateNodeApplyParams.pathToCreate->numParts(); ++i) { + updateNodeApplyParams.pathTaken->appendPart( + updateNodeApplyParams.pathToCreate->getPart(i)); } - applyParams.pathTaken->appendPart(applyParams.matchedField); + updateNodeApplyParams.pathTaken->appendPart(applyParams.matchedField); auto insertResult = _mergedChildrenCache.emplace(std::make_pair( pair.first, UpdateNode::createUpdateNodeByMerging( - *_positionalChild, *pair.second, applyParams.pathTaken.get()))); - for (size_t i = 0; i < applyParams.pathToCreate->numParts() + 1; ++i) { - applyParams.pathTaken->removeLastPart(); + *_positionalChild, *pair.second, updateNodeApplyParams.pathTaken.get()))); + for (size_t i = 0; i < updateNodeApplyParams.pathToCreate->numParts() + 1; ++i) { + updateNodeApplyParams.pathTaken->removeLastPart(); } invariant(insertResult.second); mergedChild = insertResult.first; } - applyChild(*mergedChild->second.get(), pair.first, &applyParams, &applyResult); + applyChild(*mergedChild->second.get(), + pair.first, + &applyParams, + &updateNodeApplyParams, + &applyResult); applyPositional = false; continue; @@ -439,18 +450,25 @@ UpdateNode::ApplyResult UpdateObjectNode::apply(ApplyParams applyParams) const { // If 'matchedField' is alphabetically before the current child, we should apply the // positional child now. if (applyPositional && applyParams.matchedField < pair.first) { - applyChild( - *_positionalChild.get(), applyParams.matchedField, &applyParams, &applyResult); + applyChild(*_positionalChild.get(), + applyParams.matchedField, + &applyParams, + &updateNodeApplyParams, + &applyResult); applyPositional = false; } // Apply the current child. - applyChild(*pair.second, pair.first, &applyParams, &applyResult); + applyChild(*pair.second, pair.first, &applyParams, &updateNodeApplyParams, &applyResult); } // 'matchedField' is alphabetically after all children, so we apply it now. if (applyPositional) { - applyChild(*_positionalChild.get(), applyParams.matchedField, &applyParams, &applyResult); + applyChild(*_positionalChild.get(), + applyParams.matchedField, + &applyParams, + &updateNodeApplyParams, + &applyResult); } return applyResult; diff --git a/src/mongo/db/update/update_object_node.h b/src/mongo/db/update/update_object_node.h index e09934148ca..5cbae91f1a5 100644 --- a/src/mongo/db/update/update_object_node.h +++ b/src/mongo/db/update/update_object_node.h @@ -94,7 +94,8 @@ public: } } - ApplyResult apply(ApplyParams applyParams) const final; + ApplyResult apply(ApplyParams applyParams, + UpdateNodeApplyParams updateNodeApplyParams) const final; UpdateNode* getChild(const std::string& field) const final; diff --git a/src/mongo/db/update/update_object_node_test.cpp b/src/mongo/db/update/update_object_node_test.cpp index db346004dad..0d1a37489db 100644 --- a/src/mongo/db/update/update_object_node_test.cpp +++ b/src/mongo/db/update/update_object_node_test.cpp @@ -1769,7 +1769,7 @@ TEST_F(UpdateObjectNodeTest, ApplyCreateField) { mutablebson::Document doc(fromjson("{a: 5}")); addIndexedPath("b"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 5, b: 6}"), doc); @@ -1793,7 +1793,7 @@ TEST_F(UpdateObjectNodeTest, ApplyExistingField) { mutablebson::Document doc(fromjson("{a: 5}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_EQUALS(fromjson("{a: 6}"), doc); @@ -1835,7 +1835,7 @@ TEST_F(UpdateObjectNodeTest, ApplyExistingAndNonexistingFields) { mutablebson::Document doc(fromjson("{a: 0, c: 0}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: 5, c: 7, b: 6, d: 8}"), doc.getObject()); @@ -1877,7 +1877,7 @@ TEST_F(UpdateObjectNodeTest, ApplyExistingNestedPaths) { mutablebson::Document doc(fromjson("{a: {b: 5, c: 5}, b: {d: 5, e: 5}}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: {b: 6, c: 7}, b: {d: 8, e: 9}}"), doc.getObject()); @@ -1920,7 +1920,7 @@ TEST_F(UpdateObjectNodeTest, ApplyCreateNestedPaths) { mutablebson::Document doc(fromjson("{z: 0}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{z: 0, a: {b: 6, c: 7}, b: {d: 8, e: 9}}"), doc.getObject()); @@ -1957,7 +1957,7 @@ TEST_F(UpdateObjectNodeTest, ApplyCreateDeeplyNestedPaths) { mutablebson::Document doc(fromjson("{z: 0}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{z: 0, a: {b: {c: {d: 6, e: 7}}, f: 8}}"), doc.getObject()); @@ -2006,7 +2006,7 @@ TEST_F(UpdateObjectNodeTest, ChildrenShouldBeAppliedInAlphabeticalOrder) { mutablebson::Document doc(fromjson("{z: 0, a: 0}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{z: 9, a: 5, b: 8, c: 7, d: 6}"), doc.getObject()); @@ -2038,7 +2038,7 @@ TEST_F(UpdateObjectNodeTest, CollatorShouldNotAffectUpdateOrder) { mutablebson::Document doc(fromjson("{}")); addIndexedPath("abc"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{abc: 5, cba: 6}"), doc.getObject()); @@ -2075,7 +2075,7 @@ TEST_F(UpdateObjectNodeTest, ApplyNoop) { addIndexedPath("a"); addIndexedPath("b"); addIndexedPath("c"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_TRUE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: 5, b: 6, c: 7}"), doc.getObject()); @@ -2113,7 +2113,7 @@ TEST_F(UpdateObjectNodeTest, ApplySomeChildrenNoops) { addIndexedPath("a"); addIndexedPath("b"); addIndexedPath("c"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: 5, b: 6, c: 7}"), doc.getObject()); @@ -2138,7 +2138,7 @@ TEST_F(UpdateObjectNodeTest, ApplyBlockingElement) { mutablebson::Document doc(fromjson("{a: 0}")); addIndexedPath("a"); ASSERT_EQUALS(getModifiedPaths(), "{}"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: 0}"); @@ -2166,7 +2166,7 @@ TEST_F(UpdateObjectNodeTest, ApplyBlockingElementFromReplication) { mutablebson::Document doc(fromjson("{a: 0}")); addIndexedPath("a"); setFromOplogApplication(true); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: 0, b: 6}"), doc.getObject()); @@ -2191,7 +2191,7 @@ TEST_F(UpdateObjectNodeTest, ApplyPositionalMissingMatchedField) { addIndexedPath("a"); ASSERT_EQUALS(getModifiedPaths(), "{}"); ASSERT_THROWS_CODE_AND_WHAT( - root.apply(getApplyParams(doc.root())), + root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::BadValue, "The positional operator did not find the match needed from the query."); @@ -2219,7 +2219,7 @@ TEST_F(UpdateObjectNodeTest, ApplyMergePositionalChild) { mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}]}")); setMatchedField("0"); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}]}"), doc.getObject()); @@ -2262,7 +2262,7 @@ TEST_F(UpdateObjectNodeTest, ApplyOrderMergedPositionalChild) { mutablebson::Document doc(fromjson("{}")); setMatchedField("1"); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: {'0': 7, '1': {b: 6, c: 8}, '2': 5}}"), doc.getObject()); @@ -2295,7 +2295,7 @@ TEST_F(UpdateObjectNodeTest, ApplyMergeConflictWithPositionalChild) { setMatchedField("0"); addIndexedPath("a"); ASSERT_EQUALS(getModifiedPaths(), "{}"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::ConflictingUpdateOperators, "Update created a conflict at 'a.0'"); @@ -2329,7 +2329,7 @@ TEST_F(UpdateObjectNodeTest, ApplyDoNotMergePositionalChild) { mutablebson::Document doc(fromjson("{}")); setMatchedField("1"); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: {'0': 5, '1': 7, '2': 6}}"), doc.getObject()); @@ -2366,7 +2366,7 @@ TEST_F(UpdateObjectNodeTest, ApplyPositionalChildLast) { mutablebson::Document doc(fromjson("{}")); setMatchedField("2"); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: {'0': 6, '1': 7, '2': 5}}"), doc.getObject()); @@ -2397,7 +2397,7 @@ TEST_F(UpdateObjectNodeTest, ApplyUseStoredMergedPositional) { mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}]}")); setMatchedField("0"); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}]}"), doc.getObject()); @@ -2409,7 +2409,7 @@ TEST_F(UpdateObjectNodeTest, ApplyUseStoredMergedPositional) { resetApplyParams(); setMatchedField("0"); addIndexedPath("a"); - result = root.apply(getApplyParams(doc2.root())); + result = root.apply(getApplyParams(doc2.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}]}"), doc2.getObject()); @@ -2446,7 +2446,7 @@ TEST_F(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { mutablebson::Document doc(fromjson("{a: [{b: 0, c: 0}, {c: 0, d: 0}]}")); setMatchedField("0"); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 6}, {c: 0, d: 7}]}"), doc.getObject()); @@ -2459,7 +2459,7 @@ TEST_F(UpdateObjectNodeTest, ApplyDoNotUseStoredMergedPositional) { resetApplyParams(); setMatchedField("1"); addIndexedPath("a"); - result = root.apply(getApplyParams(doc2.root())); + result = root.apply(getApplyParams(doc2.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [{b: 5, c: 0}, {c: 6, d: 7}]}"), doc2.getObject()); @@ -2489,7 +2489,7 @@ TEST_F(UpdateObjectNodeTest, ApplyToArrayByIndexWithLeadingZero) { mutablebson::Document doc(fromjson("{a: [0, 0, 0, 0, 0]}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [0, 0, 2, 0, 0]}"), doc.getObject()); @@ -2525,7 +2525,7 @@ TEST_F(UpdateObjectNodeTest, ApplyMultipleArrayUpdates) { mutablebson::Document doc(fromjson("{a: []}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ( @@ -2556,7 +2556,7 @@ TEST_F(UpdateObjectNodeTest, ApplyMultipleUpdatesToDocumentInArray) { mutablebson::Document doc(fromjson("{a: []}")); addIndexedPath("a"); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_TRUE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: [null, null, {b: 1, c: 1}]}"), doc.getObject()); @@ -2580,7 +2580,7 @@ TEST_F(UpdateObjectNodeTest, ApplyUpdateToNonViablePathInArray) { mutablebson::Document doc(fromjson("{a: [{b: 1}, {b: 2}]}")); addIndexedPath("a"); - ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root())), + ASSERT_THROWS_CODE_AND_WHAT(root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()), AssertionException, ErrorCodes::PathNotViable, "Cannot create field 'b' in element {a: [ { b: 1 }, { b: 2 } ]}"); @@ -2606,7 +2606,7 @@ TEST_F(UpdateObjectNodeTest, SetAndPopModifiersWithCommonPrefixApplySuccessfully foundIdentifiers)); mutablebson::Document doc(fromjson("{a: {b: 3, c: [1, 2, 3, 4]}}")); - auto result = root.apply(getApplyParams(doc.root())); + auto result = root.apply(getApplyParams(doc.root()), getUpdateNodeApplyParams()); ASSERT_FALSE(result.indexesAffected); ASSERT_FALSE(result.noop); ASSERT_BSONOBJ_EQ(fromjson("{a: {b: 5, c: [2, 3, 4]}}"), doc.getObject()); diff --git a/src/mongo/db/update/update_serialization_test.cpp b/src/mongo/db/update/update_serialization_test.cpp index c913689e487..046efec9825 100644 --- a/src/mongo/db/update/update_serialization_test.cpp +++ b/src/mongo/db/update/update_serialization_test.cpp @@ -56,7 +56,7 @@ auto updateRoundTrip(const char* json, const std::vector<std::string> filterName for (const auto& name : filterNames) filters[name] = nullptr; driver.parse(bson, filters); - return mongo::tojson(driver.serialize()); + return mongo::tojson(driver.serialize().getDocument().toBson()); } TEST(UpdateSerialization, DocumentReplacementSerializesExactly) { diff --git a/src/mongo/dbtests/query_stage_update.cpp b/src/mongo/dbtests/query_stage_update.cpp index c9bfa01d9f5..91b1669e050 100644 --- a/src/mongo/dbtests/query_stage_update.cpp +++ b/src/mongo/dbtests/query_stage_update.cpp @@ -212,12 +212,12 @@ public: request.setUpsert(); request.setQuery(query); - request.setUpdates(updates); + request.setUpdateModification(updates); const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW( - driver.parse(request.getUpdates(), arrayFilters, request.isMulti())); + driver.parse(request.getUpdateModification(), arrayFilters, request.isMulti())); // Setup update params. UpdateStageParams params(&request, &driver, opDebug); @@ -285,12 +285,12 @@ public: request.setMulti(); request.setQuery(query); - request.setUpdates(updates); + request.setUpdateModification(updates); const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; ASSERT_DOES_NOT_THROW( - driver.parse(request.getUpdates(), arrayFilters, request.isMulti())); + driver.parse(request.getUpdateModification(), arrayFilters, request.isMulti())); // Configure the scan. CollectionScanParams collScanParams; @@ -393,14 +393,15 @@ public: // Populate the request. request.setQuery(query); - request.setUpdates(fromjson("{$set: {x: 0}}")); + request.setUpdateModification(fromjson("{$set: {x: 0}}")); request.setSort(BSONObj()); request.setMulti(false); request.setReturnDocs(UpdateRequest::RETURN_OLD); const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; - ASSERT_DOES_NOT_THROW(driver.parse(request.getUpdates(), arrayFilters, request.isMulti())); + ASSERT_DOES_NOT_THROW( + driver.parse(request.getUpdateModification(), arrayFilters, request.isMulti())); // Configure a QueuedDataStage to pass the first object in the collection back in a // RID_AND_OBJ state. @@ -483,14 +484,15 @@ public: // Populate the request. request.setQuery(query); - request.setUpdates(fromjson("{$set: {x: 0}}")); + request.setUpdateModification(fromjson("{$set: {x: 0}}")); request.setSort(BSONObj()); request.setMulti(false); request.setReturnDocs(UpdateRequest::RETURN_NEW); const std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters; - ASSERT_DOES_NOT_THROW(driver.parse(request.getUpdates(), arrayFilters, request.isMulti())); + ASSERT_DOES_NOT_THROW( + driver.parse(request.getUpdateModification(), arrayFilters, request.isMulti())); // Configure a QueuedDataStage to pass the first object in the collection back in a // RID_AND_OBJ state. diff --git a/src/mongo/embedded/embedded_auth_session.cpp b/src/mongo/embedded/embedded_auth_session.cpp index 92b8741aa9a..f05c62af444 100644 --- a/src/mongo/embedded/embedded_auth_session.cpp +++ b/src/mongo/embedded/embedded_auth_session.cpp @@ -121,8 +121,11 @@ public: return Status::OK(); } - Status checkAuthForUpdate( - OperationContext*, const NamespaceString&, const BSONObj&, const BSONObj&, bool) override { + Status checkAuthForUpdate(OperationContext*, + const NamespaceString&, + const BSONObj&, + const write_ops::UpdateModification&, + bool) override { return Status::OK(); } diff --git a/src/mongo/embedded/stitch_support/stitch_support_test.cpp b/src/mongo/embedded/stitch_support/stitch_support_test.cpp index 4192b11219c..c81ff5a8ced 100644 --- a/src/mongo/embedded/stitch_support/stitch_support_test.cpp +++ b/src/mongo/embedded/stitch_support/stitch_support_test.cpp @@ -542,7 +542,10 @@ TEST_F(StitchSupportTest, TestUpdateWithSetOnInsert) { } TEST_F(StitchSupportTest, TestUpdateProducesProperStatus) { - ASSERT_EQ("Unknown modifier: $bogus", checkUpdateStatus("{$bogus: {a: 2}}", "{a: 1}")); + ASSERT_EQ( + "Unknown modifier: $bogus. Expected a valid update modifier or pipeline-style update " + "specified as an array", + checkUpdateStatus("{$bogus: {a: 2}}", "{a: 1}")); ASSERT_EQ("Updating the path 'a' would create a conflict at 'a'", checkUpdateStatus("{$set: {a: 2, a: 3}}", "{a: 1}")); ASSERT_EQ("No array filter found for identifier 'i' in path 'a.$[i]'", diff --git a/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp b/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp index dedf34c2c99..f660b5cf7da 100644 --- a/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp +++ b/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp @@ -1120,7 +1120,7 @@ TEST_F(DistLockCatalogTest, BasicUnlockAll) { ASSERT(update.getMulti()); ASSERT_BSONOBJ_EQ(BSON(LocksType::process("processID")), update.getQ()); ASSERT_BSONOBJ_EQ(BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))), - update.getU()); + update.getU().getUpdateClassic()); return BSON("ok" << 1); }); diff --git a/src/mongo/s/catalog/sharding_catalog_test.cpp b/src/mongo/s/catalog/sharding_catalog_test.cpp index ca6bad908ac..26e1255306c 100644 --- a/src/mongo/s/catalog/sharding_catalog_test.cpp +++ b/src/mongo/s/catalog/sharding_catalog_test.cpp @@ -1097,7 +1097,7 @@ TEST_F(ShardingCatalogClientTest, UpdateDatabase) { ASSERT(update.getUpsert()); ASSERT(!update.getMulti()); ASSERT_BSONOBJ_EQ(update.getQ(), BSON(DatabaseType::name(dbt.getName()))); - ASSERT_BSONOBJ_EQ(update.getU(), dbt.toBSON()); + ASSERT_BSONOBJ_EQ(update.getU().getUpdateClassic(), dbt.toBSON()); BatchedCommandResponse response; response.setStatus(Status::OK()); diff --git a/src/mongo/s/sharding_router_test_fixture.cpp b/src/mongo/s/sharding_router_test_fixture.cpp index 021669e79ba..7c57e6287b3 100644 --- a/src/mongo/s/sharding_router_test_fixture.cpp +++ b/src/mongo/s/sharding_router_test_fixture.cpp @@ -434,7 +434,7 @@ void ShardingTestFixture::expectUpdateCollection(const HostAndPort& expectedHost ASSERT_EQ(expectUpsert, update.getUpsert()); ASSERT(!update.getMulti()); ASSERT_BSONOBJ_EQ(BSON(CollectionType::fullNs(coll.getNs().toString())), update.getQ()); - ASSERT_BSONOBJ_EQ(coll.toBSON(), update.getU()); + ASSERT_BSONOBJ_EQ(coll.toBSON(), update.getU().getUpdateClassic()); BatchedCommandResponse response; response.setStatus(Status::OK()); diff --git a/src/mongo/s/write_ops/batch_write_op.cpp b/src/mongo/s/write_ops/batch_write_op.cpp index bb04d8323e1..27d9565a29e 100644 --- a/src/mongo/s/write_ops/batch_write_op.cpp +++ b/src/mongo/s/write_ops/batch_write_op.cpp @@ -35,6 +35,7 @@ #include "mongo/base/error_codes.h" #include "mongo/db/operation_context.h" +#include "mongo/db/ops/write_ops_parsers.h" #include "mongo/s/transaction_router.h" #include "mongo/stdx/memory.h" #include "mongo/util/transitional_tools_do_not_use/vector_spooling.h" @@ -48,11 +49,6 @@ using std::vector; namespace { -// Conservative overhead per element contained in the write batch. This value was calculated as 1 -// byte (element type) + 5 bytes (max string encoding of the array index encoded as string and the -// maximum key is 99999) + 1 byte (zero terminator) = 7 bytes -const int kBSONArrayPerElementOverheadBytes = 7; - struct WriteErrorDetailComp { bool operator()(const WriteErrorDetail* errorA, const WriteErrorDetail* errorB) const { return errorA->getIndex() < errorB->getIndex(); @@ -370,8 +366,9 @@ Status BatchWriteOp::targetBatch(const NSTargeter& targeter, // Account the array overhead once for the actual updates array and once for the statement // ids array, if retryable writes are used - const int writeSizeBytes = getWriteSizeBytes(writeOp) + kBSONArrayPerElementOverheadBytes + - (_batchTxnNum ? kBSONArrayPerElementOverheadBytes + 4 : 0); + const int writeSizeBytes = getWriteSizeBytes(writeOp) + + write_ops::kBSONArrayPerElementOverheadBytes + + (_batchTxnNum ? write_ops::kBSONArrayPerElementOverheadBytes + 4 : 0); if (wouldMakeBatchesTooBig(writes, writeSizeBytes, batchMap)) { invariant(!batchMap.empty()); diff --git a/src/mongo/s/write_ops/chunk_manager_targeter.cpp b/src/mongo/s/write_ops/chunk_manager_targeter.cpp index eb490dc6dba..770fd0c0795 100644 --- a/src/mongo/s/write_ops/chunk_manager_targeter.cpp +++ b/src/mongo/s/write_ops/chunk_manager_targeter.cpp @@ -55,14 +55,21 @@ const ShardKeyPattern kVirtualIdShardKey(BSON(kIdFieldName << 1)); using UpdateType = ChunkManagerTargeter::UpdateType; /** - * There are two styles of update expressions: + * Update expressions are bucketed into one of two types for the purposes of shard targeting: * * Replacement style: coll.update({ x : 1 }, { y : 2 }) * OpStyle: coll.update({ x : 1 }, { $set : { y : 2 } }) + * or + * coll.update({x: 1}, [{$addFields: {y: 2}}]) */ StatusWith<UpdateType> getUpdateExprType(const write_ops::UpdateOpEntry& updateDoc) { + const auto updateMod = updateDoc.getU(); + if (updateMod.type() == write_ops::UpdateModification::Type::kPipeline) { + return UpdateType::kOpStyle; + } + // Obtain the update expression from the request. - const auto updateExpr = updateDoc.getU(); + const auto& updateExpr = updateMod.getUpdateClassic(); // Empty update is replacement-style by default. auto updateType = (updateExpr.isEmpty() ? UpdateType::kReplacement : UpdateType::kUnknown); @@ -106,11 +113,16 @@ StatusWith<BSONObj> getUpdateExpr(OperationContext* opCtx, // If this is not a replacement update, then the update expression remains unchanged. if (updateType != UpdateType::kReplacement) { - return updateDoc.getU(); + const auto& updateMod = updateDoc.getU(); + BSONObjBuilder objBuilder; + updateMod.serializeToBSON("u", &objBuilder); + return objBuilder.obj(); } // Extract the raw update expression from the request. - auto updateExpr = updateDoc.getU(); + const auto& updateMod = updateDoc.getU(); + invariant(updateMod.type() == write_ops::UpdateModification::Type::kClassic); + auto updateExpr = updateMod.getUpdateClassic(); // Find the set of all shard key fields that are missing from the update expression. const auto missingFields = shardKeyPattern.findMissingShardKeyFieldsFromDoc(updateExpr); @@ -402,10 +414,10 @@ StatusWith<std::vector<ShardEndpoint>> ChunkManagerTargeter::targetUpdate( // Because drivers do not know the shard key, they can't pull the shard key automatically // into the query doc, and to correctly support upsert we must target a single shard. // - // The rule is simple - If the update is replacement style (no '$set'), we target using the - // update. If the update is not replacement style, we target using the query. Because mongoD - // will automatically propagate '_id' from an existing document, and will extract it from an - // exact-match in the query in the case of an upsert, we augment the replacement doc with the + // The rule is simple - If the update is replacement style (no '$set' or pipeline), we target + // using the update. If the update is not replacement style, we target using the query. Because + // mongoD will automatically propagate '_id' from an existing document, and will extract it from + // an exact-match in the query in the case of an upsert, we augment the replacement doc with the // query's '_id' for targeting purposes, if it exists. // // Once we have determined the correct component to target on, we attempt to extract an exact |