summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorJames Wahlin <james@mongodb.com>2019-03-07 08:30:27 -0500
committerJames Wahlin <james@mongodb.com>2019-04-11 14:59:55 -0400
commit6b47868e5a82822a21176db3a7d3abd2df429e1f (patch)
treeca2adddd4590b62cdefbc641d97cc58b5cc05479 /src/mongo
parent95bb948f7e5e573ca1473ba43dd6fd8e53cb5f50 (diff)
downloadmongo-6b47868e5a82822a21176db3a7d3abd2df429e1f.tar.gz
SERVER-40381 Add the ability to specify a pipeline to an update command
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/audit.cpp2
-rw-r--r--src/mongo/db/audit.h3
-rw-r--r--src/mongo/db/auth/authorization_session.h3
-rw-r--r--src/mongo/db/auth/authorization_session_impl.cpp2
-rw-r--r--src/mongo/db/auth/authorization_session_impl.h2
-rw-r--r--src/mongo/db/commands/find_and_modify.cpp2
-rw-r--r--src/mongo/db/commands/write_commands/write_commands.cpp2
-rw-r--r--src/mongo/db/dbhelpers.cpp4
-rw-r--r--src/mongo/db/exec/plan_stats.h10
-rw-r--r--src/mongo/db/exec/update_stage.cpp8
-rw-r--r--src/mongo/db/ops/SConscript1
-rw-r--r--src/mongo/db/ops/parsed_update.cpp8
-rw-r--r--src/mongo/db/ops/update_request.h13
-rw-r--r--src/mongo/db/ops/write_ops.idl10
-rw-r--r--src/mongo/db/ops/write_ops_exec.cpp16
-rw-r--r--src/mongo/db/ops/write_ops_parsers.cpp53
-rw-r--r--src/mongo/db/ops/write_ops_parsers.h82
-rw-r--r--src/mongo/db/ops/write_ops_parsers_test.cpp79
-rw-r--r--src/mongo/db/pipeline/document_source_graph_lookup_test.cpp35
-rw-r--r--src/mongo/db/pipeline/document_source_lookup_test.cpp78
-rw-r--r--src/mongo/db/pipeline/document_source_single_document_transformation.h1
-rw-r--r--src/mongo/db/pipeline/expression_context.h8
-rw-r--r--src/mongo/db/pipeline/lite_parsed_pipeline.h3
-rw-r--r--src/mongo/db/pipeline/pipeline_metadata_tree_test.cpp6
-rw-r--r--src/mongo/db/pipeline/stage_constraints.h3
-rw-r--r--src/mongo/db/repl/oplog.cpp4
-rw-r--r--src/mongo/db/repl/rs_rollback.cpp2
-rw-r--r--src/mongo/db/repl/storage_interface_impl.cpp6
-rw-r--r--src/mongo/db/repl/storage_interface_impl_test.cpp3
-rw-r--r--src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp6
-rw-r--r--src/mongo/db/s/sharding_initialization_mongod.cpp2
-rw-r--r--src/mongo/db/s/sharding_state_recovery.cpp2
-rw-r--r--src/mongo/db/transaction_participant.cpp12
-rw-r--r--src/mongo/db/update/SConscript8
-rw-r--r--src/mongo/db/update/addtoset_node_test.cpp36
-rw-r--r--src/mongo/db/update/arithmetic_node_test.cpp173
-rw-r--r--src/mongo/db/update/bit_node_test.cpp18
-rw-r--r--src/mongo/db/update/compare_node_test.cpp38
-rw-r--r--src/mongo/db/update/conflict_placeholder_node.h3
-rw-r--r--src/mongo/db/update/current_date_node_test.cpp14
-rw-r--r--src/mongo/db/update/modifier_node.cpp76
-rw-r--r--src/mongo/db/update/modifier_node.h9
-rw-r--r--src/mongo/db/update/object_replace_node.cpp26
-rw-r--r--src/mongo/db/update/object_replace_node.h34
-rw-r--r--src/mongo/db/update/object_replace_node_test.cpp36
-rw-r--r--src/mongo/db/update/pipeline_executor.cpp87
-rw-r--r--src/mongo/db/update/pipeline_executor.h91
-rw-r--r--src/mongo/db/update/pipeline_executor_test.cpp289
-rw-r--r--src/mongo/db/update/pop_node_test.cpp43
-rw-r--r--src/mongo/db/update/pull_node_test.cpp86
-rw-r--r--src/mongo/db/update/pullall_node_test.cpp33
-rw-r--r--src/mongo/db/update/push_node_test.cpp75
-rw-r--r--src/mongo/db/update/rename_node.cpp18
-rw-r--r--src/mongo/db/update/rename_node.h3
-rw-r--r--src/mongo/db/update/rename_node_test.cpp83
-rw-r--r--src/mongo/db/update/set_node_test.cpp200
-rw-r--r--src/mongo/db/update/unset_node_test.cpp55
-rw-r--r--src/mongo/db/update/update_array_node.cpp28
-rw-r--r--src/mongo/db/update/update_array_node.h3
-rw-r--r--src/mongo/db/update/update_array_node_test.cpp46
-rw-r--r--src/mongo/db/update/update_driver.cpp92
-rw-r--r--src/mongo/db/update/update_driver.h68
-rw-r--r--src/mongo/db/update/update_driver_test.cpp27
-rw-r--r--src/mongo/db/update/update_executor.h128
-rw-r--r--src/mongo/db/update/update_node.h94
-rw-r--r--src/mongo/db/update/update_node_test_fixture.h13
-rw-r--r--src/mongo/db/update/update_node_visitor.h2
-rw-r--r--src/mongo/db/update/update_object_node.cpp82
-rw-r--r--src/mongo/db/update/update_object_node.h3
-rw-r--r--src/mongo/db/update/update_object_node_test.cpp54
-rw-r--r--src/mongo/db/update/update_serialization_test.cpp2
-rw-r--r--src/mongo/dbtests/query_stage_update.cpp18
-rw-r--r--src/mongo/embedded/embedded_auth_session.cpp7
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support_test.cpp5
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp2
-rw-r--r--src/mongo/s/catalog/sharding_catalog_test.cpp2
-rw-r--r--src/mongo/s/sharding_router_test_fixture.cpp2
-rw-r--r--src/mongo/s/write_ops/batch_write_op.cpp11
-rw-r--r--src/mongo/s/write_ops/chunk_manager_targeter.cpp28
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