diff options
author | Arun Banala <arun.banala@mongodb.com> | 2020-11-16 17:38:10 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-12-02 18:37:38 +0000 |
commit | 864d81272c7abe7b411a569001e9f809b8407687 (patch) | |
tree | 8fda7a4b53cb93cf80803ea62b77e3f9459535da | |
parent | 6446fe2ed7298f0e91476384fc7fcf2abc9b9328 (diff) | |
download | mongo-864d81272c7abe7b411a569001e9f809b8407687.tar.gz |
SERVER-51620 Convert findAndModify input to IDL
19 files changed, 394 insertions, 1635 deletions
diff --git a/jstests/core/command_json_schema_field.js b/jstests/core/command_json_schema_field.js index 0487e6ac451..7015cad289a 100644 --- a/jstests/core/command_json_schema_field.js +++ b/jstests/core/command_json_schema_field.js @@ -30,7 +30,7 @@ assertCommandFailsWithCorrectError({find: coll.getName(), jsonSchema: {}}, // FindAndModify assertCommandFailsWithCorrectError( {findAndModify: coll.getName(), query: {_id: 0}, remove: true, jsonSchema: {}}, - ErrorCodes.FailedToParse); + [ErrorCodes.FailedToParse, 4662500]); // Count assertCommandFailsWithCorrectError({count: coll.getName(), jsonSchema: {}}, 4662500); diff --git a/jstests/core/explain_uuid.js b/jstests/core/explain_uuid.js index bee56d4b2a9..84d67074c8d 100644 --- a/jstests/core/explain_uuid.js +++ b/jstests/core/explain_uuid.js @@ -36,7 +36,7 @@ assert.commandFailedWithCode(explainDB.runCommand({explain: {distinct: uuid, key const expectedCode = TestData.auth ? 17137 : ErrorCodes.InvalidNamespace; assert.commandFailedWithCode( explainDB.runCommand({explain: {findAndModify: uuid, query: {a: 1}, remove: true}}), - expectedCode); + [expectedCode, ErrorCodes.BadValue]); assert.commandFailedWithCode( explainDB.runCommand({explain: {delete: uuid, deletes: [{q: {}, limit: 1}]}}), diff --git a/jstests/core/find_and_modify_invalid_query_params.js b/jstests/core/find_and_modify_invalid_query_params.js index 6c5d16c94f1..ad8ae4439b5 100644 --- a/jstests/core/find_and_modify_invalid_query_params.js +++ b/jstests/core/find_and_modify_invalid_query_params.js @@ -1,7 +1,8 @@ /** * Test that the 'findAndModify' command throws the expected errors for invalid query, sort and * projection parameters. This test exercises the fix for SERVER-41829. - * @tags: [assumes_unsharded_collection] + * + * @tags: [assumes_unsharded_collection, requires_fcv_49] */ (function() { "use strict"; @@ -12,8 +13,8 @@ coll.insert({_id: 0}); coll.insert({_id: 1}); function assertFailedWithCode(cmd, errorCode) { - const err = assert.throws(() => coll.findAndModify(cmd)); - assert.eq(err.code, errorCode); + assert.commandFailedWithCode( + coll.runCommand(Object.merge({findAndModify: coll.getName()}, cmd)), errorCode); } function assertWorked(cmd, expectedValue) { @@ -24,12 +25,12 @@ function assertWorked(cmd, expectedValue) { // Verify that the findAndModify command works when we supply a valid query. let out = coll.findAndModify({query: {_id: 1}, update: {$set: {value: "basic"}}, new: true}); assert.eq(out, {_id: 1, value: "basic"}); +assertWorked({query: null, update: {value: 2}, new: true}, 2); // Verify that invalid 'query' object fails. -assertFailedWithCode({query: null, update: {value: 2}}, 31160); -assertFailedWithCode({query: 1, update: {value: 2}}, 31160); -assertFailedWithCode({query: "{_id: 1}", update: {value: 2}}, 31160); -assertFailedWithCode({query: false, update: {value: 2}}, 31160); +assertFailedWithCode({query: 1, update: {value: 2}}, ErrorCodes.TypeMismatch); +assertFailedWithCode({query: "{_id: 1}", update: {value: 2}}, ErrorCodes.TypeMismatch); +assertFailedWithCode({query: false, update: {value: 2}}, ErrorCodes.TypeMismatch); // Verify that missing and empty query object is allowed. assertWorked({update: {$set: {value: "missingQuery"}}, new: true}, "missingQuery"); @@ -37,12 +38,12 @@ assertWorked({query: {}, update: {$set: {value: "emptyQuery"}}, new: true}, "emp // Verify that command works when we supply a valid sort specification. assertWorked({sort: {_id: -1}, update: {$set: {value: "sort"}}, new: true}, "sort"); +assertWorked({sort: null, update: {value: 2}, new: true}, 2); // Verify that invaid 'sort' object fails. -assertFailedWithCode({sort: null, update: {value: 2}}, 31174); -assertFailedWithCode({sort: 1, update: {value: 2}}, 31174); -assertFailedWithCode({sort: "{_id: 1}", update: {value: 2}}, 31174); -assertFailedWithCode({sort: false, update: {value: 2}}, 31174); +assertFailedWithCode({sort: 1, update: {value: 2}}, ErrorCodes.TypeMismatch); +assertFailedWithCode({sort: "{_id: 1}", update: {value: 2}}, ErrorCodes.TypeMismatch); +assertFailedWithCode({sort: false, update: {value: 2}}, ErrorCodes.TypeMismatch); // Verify that missing and empty 'sort' object is allowed. assertWorked({update: {$set: {value: "missingSort"}}, new: true}, "missingSort"); @@ -50,12 +51,12 @@ assertWorked({sort: {}, update: {$set: {value: "emptySort"}}, new: true}, "empty // Verify that the 'fields' projection works. assertWorked({fields: {_id: 0}, update: {$set: {value: "project"}}, new: true}, "project"); +assertWorked({fields: null, update: {value: 2}, new: true}, 2); // Verify that invaid 'fields' object fails. -assertFailedWithCode({fields: null, update: {value: 2}}, 31175); -assertFailedWithCode({fields: 1, update: {value: 2}}, 31175); -assertFailedWithCode({fields: "{_id: 1}", update: {value: 2}}, 31175); -assertFailedWithCode({fields: false, update: {value: 2}}, 31175); +assertFailedWithCode({fields: 1, update: {value: 2}}, ErrorCodes.TypeMismatch); +assertFailedWithCode({fields: "{_id: 1}", update: {value: 2}}, ErrorCodes.TypeMismatch); +assertFailedWithCode({fields: false, update: {value: 2}}, ErrorCodes.TypeMismatch); // Verify that missing and empty 'fields' object is allowed. Also verify that the command // projects all the fields. @@ -64,21 +65,21 @@ assertWorked({fields: {}, update: {$set: {value: "emptyFields"}}, new: true}, "e // Verify that findOneAndDelete() shell helper throws the same errors as findAndModify(). let err = assert.throws(() => coll.findOneAndDelete("{_id: 1}")); -assert.eq(err.code, 31160); +assert(err.code == ErrorCodes.TypeMismatch, err); err = assert.throws(() => coll.findOneAndDelete(null, {sort: 1})); -assert.eq(err.code, 31174); +assert(err.code == ErrorCodes.TypeMismatch, err); // Verify that findOneAndReplace() shell helper throws the same errors as findAndModify(). err = assert.throws(() => coll.findOneAndReplace("{_id: 1}", {})); -assert.eq(err.code, 31160); +assert(err.code == ErrorCodes.TypeMismatch, err); err = assert.throws(() => coll.findOneAndReplace(null, {}, {sort: 1})); -assert.eq(err.code, 31174); +assert(err.code == ErrorCodes.TypeMismatch, err); // Verify that findOneAndUpdate() shell helper throws the same errors as findAndModify(). err = assert.throws(() => coll.findOneAndUpdate("{_id: 1}", {$set: {value: "new"}})); -assert.eq(err.code, 31160); +assert(err.code == ErrorCodes.TypeMismatch, err); err = assert.throws(() => coll.findOneAndUpdate(null, {$set: {value: "new"}}, {sort: 1})); -assert.eq(err.code, 31174); +assert(err.code == ErrorCodes.TypeMismatch, err); // Verify that find and modify shell helpers allow null query object. out = coll.findOneAndUpdate(null, {$set: {value: "findOneAndUpdate"}}, {returnNewDocument: true}); @@ -89,4 +90,46 @@ assert.eq(out.value, "findOneAndReplace"); out = coll.findOneAndDelete(null); assert.eq(out.value, "findOneAndReplace"); + +// Incompatiable parameters with 'remove'. +coll.drop(); +coll.insert({_id: 0}); +assertFailedWithCode({update: {value: 2}, remove: true}, ErrorCodes.FailedToParse); +assertFailedWithCode({query: {_id: 1}, upsert: true, remove: true}, ErrorCodes.FailedToParse); +assertFailedWithCode({new: true, remove: true}, ErrorCodes.FailedToParse); +assertFailedWithCode({update: {}, arrayFilters: [{"x.a": {$gt: 85}}], remove: true}, + ErrorCodes.FailedToParse); + +// Verify that the above updates succeed when the boolean values are set to 'false' or '0'. +assertWorked({update: {value: "newVal"}, new: true, remove: false}, "newVal"); +assertWorked({update: {value: "newerVal"}, new: false, remove: 0}, "newVal"); +assertWorked({query: {_id: 1}, update: {value: "newerVal"}, new: 1, remove: false, upsert: 1}, + "newerVal"); +assertWorked( + {update: {value: "newestVal"}, new: false, arrayFilters: [{"x.a": {$gt: 85}}], remove: 0.0}, + "newerVal"); + +out = coll.findAndModify( + {query: {_id: 2}, update: {value: "newVal"}, new: false, remove: false, upsert: true}); +assert.eq(out, null); + +// Pipeline update with 'arrayFilters'. +assertFailedWithCode({update: [], arrayFilters: [{"x.a": {$gt: 85}}]}, ErrorCodes.FailedToParse); + +// Invalid value for hint. +assertFailedWithCode({query: {_id: 0}, update: {value: "usedHint"}, hint: 1}, + ErrorCodes.FailedToParse); + +// Hint with string and object. +coll.createIndex({value: 1}, {name: 'value'}); +assertWorked({query: {_id: 0}, update: {value: "usedHint"}, new: 1.0, hint: "value", remove: 0.0}, + "usedHint"); +assertWorked({ + query: {_id: 0, value: "usedHint"}, + update: {value: "newValue"}, + new: 0, + hint: {_id: 1}, + remove: 0 +}, + "usedHint"); })(); diff --git a/jstests/replsets/lastop.js b/jstests/replsets/lastop.js index 1abcd15abeb..87c52f4f04d 100644 --- a/jstests/replsets/lastop.js +++ b/jstests/replsets/lastop.js @@ -47,7 +47,7 @@ assert.commandWorked(m2.getCollection("test.foo").insert({m2: 97})); var sixthOp = m2.getCollection("test.foo").getDB().getLastErrorObj().lastOp; // No-op find-and-modify delete -m1.getCollection("test.foo").findAndModify({query: {m1: 1}, remove: 'true'}); +m1.getCollection("test.foo").findAndModify({query: {m1: 1}, remove: true}); noOp = m1.getCollection("test.foo").getDB().getLastErrorObj().lastOp; assert.eq(noOp, sixthOp); diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index 9f0c93060bf..fbfe5d10fd3 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -62,7 +62,6 @@ #include "mongo/db/ops/write_ops_retryability.h" #include "mongo/db/query/collection_query_info.h" #include "mongo/db/query/explain.h" -#include "mongo/db/query/find_and_modify_request.h" #include "mongo/db/query/get_executor.h" #include "mongo/db/query/plan_executor.h" #include "mongo/db/query/plan_summary_stats.h" @@ -119,24 +118,54 @@ boost::optional<BSONObj> advanceExecutor(OperationContext* opCtx, return boost::none; } +void validate(const write_ops::FindAndModifyCommand& request) { + uassert(ErrorCodes::FailedToParse, + "Either an update or remove=true must be specified", + request.getRemove().value_or(false) || request.getUpdate()); + if (request.getRemove().value_or(false)) { + uassert(ErrorCodes::FailedToParse, + "Cannot specify both an update and remove=true", + !request.getUpdate()); + + uassert(ErrorCodes::FailedToParse, + "Cannot specify both upsert=true and remove=true ", + !request.getUpsert() || !*request.getUpsert()); + + uassert(ErrorCodes::FailedToParse, + "Cannot specify both new=true and remove=true; 'remove' always returns the deleted " + "document", + !request.getNew() || !*request.getNew()); + + uassert(ErrorCodes::FailedToParse, + "Cannot specify arrayFilters and remove=true", + !request.getArrayFilters()); + } + + if (request.getUpdate() && + request.getUpdate()->type() == write_ops::UpdateModification::Type::kPipeline && + request.getArrayFilters()) { + uasserted(ErrorCodes::FailedToParse, "Cannot specify arrayFilters and a pipeline update"); + } +} + void makeUpdateRequest(OperationContext* opCtx, - const FindAndModifyRequest& args, + const write_ops::FindAndModifyCommand& request, boost::optional<ExplainOptions::Verbosity> explain, UpdateRequest* requestOut) { - requestOut->setQuery(args.getQuery()); - requestOut->setProj(args.getFields()); - invariant(args.getUpdate()); - requestOut->setUpdateModification(*args.getUpdate()); + requestOut->setQuery(request.getQuery()); + requestOut->setProj(request.getFields().value_or(BSONObj())); + invariant(request.getUpdate()); + requestOut->setUpdateModification(*request.getUpdate()); requestOut->setLegacyRuntimeConstants( - args.getLegacyRuntimeConstants().value_or(Variables::generateRuntimeConstants(opCtx))); - requestOut->setLetParameters(args.getLetParameters()); - requestOut->setSort(args.getSort()); - requestOut->setHint(args.getHint()); - requestOut->setCollation(args.getCollation()); - requestOut->setArrayFilters(args.getArrayFilters()); - requestOut->setUpsert(args.isUpsert()); - requestOut->setReturnDocs(args.shouldReturnNew() ? UpdateRequest::RETURN_NEW - : UpdateRequest::RETURN_OLD); + request.getLegacyRuntimeConstants().value_or(Variables::generateRuntimeConstants(opCtx))); + requestOut->setLetParameters(request.getLet()); + requestOut->setSort(request.getSort().value_or(BSONObj())); + requestOut->setHint(request.getHint()); + requestOut->setCollation(request.getCollation().value_or(BSONObj())); + requestOut->setArrayFilters(request.getArrayFilters().value_or(std::vector<BSONObj>())); + requestOut->setUpsert(request.getUpsert().value_or(false)); + requestOut->setReturnDocs((request.getNew().value_or(false)) ? UpdateRequest::RETURN_NEW + : UpdateRequest::RETURN_OLD); requestOut->setMulti(false); requestOut->setExplain(explain); @@ -146,17 +175,17 @@ void makeUpdateRequest(OperationContext* opCtx, } void makeDeleteRequest(OperationContext* opCtx, - const FindAndModifyRequest& args, + const write_ops::FindAndModifyCommand& request, bool explain, DeleteRequest* requestOut) { - requestOut->setQuery(args.getQuery()); - requestOut->setProj(args.getFields()); + requestOut->setQuery(request.getQuery()); + requestOut->setProj(request.getFields().value_or(BSONObj())); requestOut->setLegacyRuntimeConstants( - args.getLegacyRuntimeConstants().value_or(Variables::generateRuntimeConstants(opCtx))); - requestOut->setLet(args.getLetParameters()); - requestOut->setSort(args.getSort()); - requestOut->setHint(args.getHint()); - requestOut->setCollation(args.getCollation()); + request.getLegacyRuntimeConstants().value_or(Variables::generateRuntimeConstants(opCtx))); + requestOut->setLet(request.getLet()); + requestOut->setSort(request.getSort().value_or(BSONObj())); + requestOut->setHint(request.getHint()); + requestOut->setCollation(request.getCollation().value_or(BSONObj())); requestOut->setMulti(false); requestOut->setReturnDeleted(true); // Always return the old value. requestOut->setIsExplain(explain); @@ -264,25 +293,27 @@ public: } Status explain(OperationContext* opCtx, - const OpMsgRequest& request, + const OpMsgRequest& opMsgRequest, ExplainOptions::Verbosity verbosity, rpc::ReplyBuilderInterface* result) const override { - std::string dbName = request.getDatabase().toString(); - const BSONObj& cmdObj = request.body; - const auto args(uassertStatusOK(FindAndModifyRequest::parseFromBSON( - CommandHelpers::parseNsCollectionRequired(dbName, cmdObj), cmdObj))); - const NamespaceString& nsString = args.getNamespaceString(); + std::string dbName = opMsgRequest.getDatabase().toString(); + const BSONObj& cmdObj = opMsgRequest.body; + const auto request( + write_ops::FindAndModifyCommand::parse(IDLParserErrorContext("findAndModify"), cmdObj)); + validate(request); + + const NamespaceString& nsString = request.getNamespace(); uassertStatusOK(userAllowedWriteNS(nsString)); auto const curOp = CurOp::get(opCtx); OpDebug* const opDebug = &curOp->debug(); - if (args.isRemove()) { - auto request = DeleteRequest{}; - request.setNsString(nsString); + if (request.getRemove().value_or(false)) { + auto deleteRequest = DeleteRequest{}; + deleteRequest.setNsString(nsString); const bool isExplain = true; - makeDeleteRequest(opCtx, args, isExplain, &request); + makeDeleteRequest(opCtx, request, isExplain, &deleteRequest); - ParsedDelete parsedDelete(opCtx, &request); + ParsedDelete parsedDelete(opCtx, &deleteRequest); uassertStatusOK(parsedDelete.parseRequest()); // Explain calls of the findAndModify command are read-only, but we take write @@ -301,12 +332,13 @@ public: Explain::explainStages( exec.get(), collection.getCollection(), verbosity, BSONObj(), cmdObj, &bodyBuilder); } else { - auto request = UpdateRequest(); - request.setNamespaceString(nsString); - makeUpdateRequest(opCtx, args, verbosity, &request); + auto updateRequest = UpdateRequest(); + updateRequest.setNamespaceString(nsString); + makeUpdateRequest(opCtx, request, verbosity, &updateRequest); - const ExtensionsCallbackReal extensionsCallback(opCtx, &request.getNamespaceString()); - ParsedUpdate parsedUpdate(opCtx, &request, extensionsCallback); + const ExtensionsCallbackReal extensionsCallback(opCtx, + &updateRequest.getNamespaceString()); + ParsedUpdate parsedUpdate(opCtx, &updateRequest, extensionsCallback); uassertStatusOK(parsedUpdate.parseRequest()); // Explain calls of the findAndModify command are read-only, but we take write @@ -333,9 +365,11 @@ public: const std::string& dbName, const BSONObj& cmdObj, BSONObjBuilder& result) override { - const auto args(uassertStatusOK(FindAndModifyRequest::parseFromBSON( - CommandHelpers::parseNsCollectionRequired(dbName, cmdObj), cmdObj))); - const NamespaceString& nsString = args.getNamespaceString(); + const auto request( + write_ops::FindAndModifyCommand::parse(IDLParserErrorContext("findAndModify"), cmdObj)); + validate(request); + + const NamespaceString& nsString = request.getNamespace(); uassertStatusOK(userAllowedWriteNS(nsString)); auto const curOp = CurOp::get(opCtx); OpDebug* const opDebug = &curOp->debug(); @@ -367,7 +401,7 @@ public: if (auto entry = txnParticipant.checkStatementExecuted(opCtx, stmtId)) { RetryableWritesStats::get(opCtx)->incrementRetriedCommandsCount(); RetryableWritesStats::get(opCtx)->incrementRetriedStatementsCount(); - parseOplogEntryForFindAndModify(opCtx, args, *entry, &result); + parseOplogEntryForFindAndModify(opCtx, request, *entry, &result); // Make sure to wait for writeConcern on the opTime that will include this write. // Needs to set to the system last opTime to get the latest term in an event when @@ -383,9 +417,16 @@ public: // executing a findAndModify. This is done to ensure that we can always match, modify, and // return the document under concurrency, if a matching document exists. return writeConflictRetry(opCtx, "findAndModify", nsString.ns(), [&] { - if (args.isRemove()) { - return writeConflictRetryRemove( - opCtx, nsString, args, stmtId, curOp, opDebug, inTransaction, cmdObj, result); + if (request.getRemove().value_or(false)) { + return writeConflictRetryRemove(opCtx, + nsString, + request, + stmtId, + curOp, + opDebug, + inTransaction, + cmdObj, + result); } else { if (MONGO_unlikely(hangBeforeFindAndModifyPerformsUpdate.shouldFail())) { CurOpFailpointHelpers::waitWhileFailPointEnabled( @@ -397,24 +438,24 @@ public: // Nested retry loop to handle concurrent conflicting upserts with equality match. int retryAttempts = 0; for (;;) { - auto request = UpdateRequest(); - request.setNamespaceString(nsString); + auto updateRequest = UpdateRequest(); + updateRequest.setNamespaceString(nsString); const auto verbosity = boost::none; - makeUpdateRequest(opCtx, args, verbosity, &request); + makeUpdateRequest(opCtx, request, verbosity, &updateRequest); if (opCtx->getTxnNumber()) { - request.setStmtId(stmtId); + updateRequest.setStmtId(stmtId); } - const ExtensionsCallbackReal extensionsCallback(opCtx, - &request.getNamespaceString()); - ParsedUpdate parsedUpdate(opCtx, &request, extensionsCallback); + const ExtensionsCallbackReal extensionsCallback( + opCtx, &updateRequest.getNamespaceString()); + ParsedUpdate parsedUpdate(opCtx, &updateRequest, extensionsCallback); uassertStatusOK(parsedUpdate.parseRequest()); try { return writeConflictRetryUpsert(opCtx, nsString, - args, + request, curOp, opDebug, inTransaction, @@ -448,23 +489,23 @@ public: static bool writeConflictRetryRemove(OperationContext* opCtx, const NamespaceString& nsString, - const FindAndModifyRequest& args, + const write_ops::FindAndModifyCommand& request, const int stmtId, CurOp* const curOp, OpDebug* const opDebug, const bool inTransaction, const BSONObj& cmdObj, BSONObjBuilder& result) { - auto request = DeleteRequest{}; - request.setNsString(nsString); + auto deleteRequest = DeleteRequest{}; + deleteRequest.setNsString(nsString); const bool isExplain = false; - makeDeleteRequest(opCtx, args, isExplain, &request); + makeDeleteRequest(opCtx, request, isExplain, &deleteRequest); if (opCtx->getTxnNumber()) { - request.setStmtId(stmtId); + deleteRequest.setStmtId(stmtId); } - ParsedDelete parsedDelete(opCtx, &request); + ParsedDelete parsedDelete(opCtx, &deleteRequest); uassertStatusOK(parsedDelete.parseRequest()); AutoGetCollection collection(opCtx, nsString, MODE_IX); @@ -488,7 +529,8 @@ public: CurOp::get(opCtx)->setPlanSummary_inlock(exec->getPlanExplainer().getPlanSummary()); } - auto docFound = advanceExecutor(opCtx, cmdObj, exec.get(), args.isRemove()); + auto docFound = + advanceExecutor(opCtx, cmdObj, exec.get(), request.getRemove().value_or(false)); // Nothing after advancing the plan executor should throw a WriteConflictException, // so the following bookkeeping with execution stats won't end up being done // multiple times. @@ -511,14 +553,14 @@ public: } recordStatsForTopCommand(opCtx); - appendCommandResponse(exec.get(), args.isRemove(), docFound, &result); + appendCommandResponse(exec.get(), request.getRemove().value_or(false), docFound, &result); return true; } static bool writeConflictRetryUpsert(OperationContext* opCtx, const NamespaceString& nsString, - const FindAndModifyRequest& args, + const write_ops::FindAndModifyCommand& request, CurOp* const curOp, OpDebug* const opDebug, const bool inTransaction, @@ -543,7 +585,7 @@ public: // TODO SERVER-50983: Create abstraction for creating collection when using // AutoGetCollection Create the collection if it does not exist when performing an upsert // because the update stage does not create its own collection - if (!*collectionPtr && args.isUpsert()) { + if (!*collectionPtr && request.getUpsert() && *request.getUpsert()) { assertCanWrite(opCtx, nsString); createdCollection = @@ -576,7 +618,8 @@ public: CurOp::get(opCtx)->setPlanSummary_inlock(exec->getPlanExplainer().getPlanSummary()); } - auto docFound = advanceExecutor(opCtx, cmdObj, exec.get(), args.isRemove()); + auto docFound = + advanceExecutor(opCtx, cmdObj, exec.get(), request.getRemove().value_or(false)); // Nothing after advancing the plan executor should throw a WriteConflictException, // so the following bookkeeping with execution stats won't end up being done // multiple times. @@ -597,7 +640,7 @@ public: } recordStatsForTopCommand(opCtx); - appendCommandResponse(exec.get(), args.isRemove(), docFound, &result); + appendCommandResponse(exec.get(), request.getRemove().value_or(false), docFound, &result); return true; } @@ -612,8 +655,9 @@ public: }(); bob->append("find", cmdObj.firstElement().String()); - if (cmdObj.hasField("query")) { - bob->append("filter", cmdObj["query"].Obj()); + auto queryField = cmdObj["query"]; + if (queryField.type() == BSONType::Object) { + bob->append("filter", queryField.Obj()); } cmdObj.filterFieldsUndotted(bob, kMirrorableKeys, true); diff --git a/src/mongo/db/ops/SConscript b/src/mongo/db/ops/SConscript index 3b7ad76017e..c86b8efef22 100644 --- a/src/mongo/db/ops/SConscript +++ b/src/mongo/db/ops/SConscript @@ -29,6 +29,7 @@ env.Library( env.Library( target='write_ops_parsers', source=[ + 'find_and_modify_command.idl', 'write_ops_parsers.cpp', 'write_ops.idl', ], diff --git a/src/mongo/db/ops/find_and_modify_command.idl b/src/mongo/db/ops/find_and_modify_command.idl new file mode 100644 index 00000000000..fde8bc3c037 --- /dev/null +++ b/src/mongo/db/ops/find_and_modify_command.idl @@ -0,0 +1,112 @@ +# Copyright (C) 2020-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. +# + +global: + cpp_namespace: "mongo::write_ops" + cpp_includes: + - "mongo/db/ops/write_ops_parsers.h" + +imports: + - "mongo/db/ops/write_ops.idl" + - "mongo/db/pipeline/legacy_runtime_constants.idl" + - "mongo/idl/basic_types.idl" + - "mongo/db/query/hint.idl" + +commands: + findAndModify: + description: "Parser for the 'findAndModify' command." + command_name: findAndModify + cpp_name: FindAndModifyCommand + strict: true + namespace: concatenate_with_db + fields: + query: + description: "The query that matches documents to update. Uses the same query + selectors as used in the 'find' operation." + type: object_owned + default: mongo::BSONObj() + fields: + description: "A subset of fields to return." + type: object_owned + optional: true + sort: + description: "Determines which document the operation modifies if the query selects + multiple documents." + type: object_owned + optional: true + hint: + description: "Specifies the hint to use for the operation." + type: indexHint + default: mongo::BSONObj() + collation: + description: "Specifies the collation to use for the operation." + type: object + optional: true + arrayFilters: + description: "Specifies which array elements an update modifier should apply to." + type: array<object> + optional: true + remove: + description: "Removes the document specified in the query field." + # We use 'safeBool' here since the field also allows numeric values. + type: safeBool + optional: true + update: + description: "Modification to apply." + type: update_modification + optional: true + upsert: + description: "If true, perform an insert if no documents match the query. If both + upsert and multi are true and no documents match the query, the update + operation inserts only a single document." + type: safeBool + optional: true + new: + description: "Removes the document specified in the query field." + type: safeBool + optional: true + bypassDocumentValidation: + description: "Enables the operation to bypass document validation. This lets you + write documents that do not meet the validation requirements." + type: safeBool + optional: true + let: + description: "A set of user-specified constants used by pipeline-style update + operations and $expr." + type: object + optional: true + runtimeConstants: + description: "A collection of values that do not change once computed. These are + used by pipeline-style update operations." + cpp_name: legacyRuntimeConstants + type: LegacyRuntimeConstants + optional: true + writeConcern: + description: "Describes the write concern." + type: object + optional: true
\ No newline at end of file diff --git a/src/mongo/db/ops/write_ops.idl b/src/mongo/db/ops/write_ops.idl index 6fce935cf11..06195f1fe50 100644 --- a/src/mongo/db/ops/write_ops.idl +++ b/src/mongo/db/ops/write_ops.idl @@ -219,4 +219,4 @@ commands: 'let' is now preferred." cpp_name: legacyRuntimeConstants type: LegacyRuntimeConstants - optional: true + optional: true
\ No newline at end of file diff --git a/src/mongo/db/ops/write_ops_retryability.cpp b/src/mongo/db/ops/write_ops_retryability.cpp index 252b417d92f..ba12fe0c453 100644 --- a/src/mongo/db/ops/write_ops_retryability.cpp +++ b/src/mongo/db/ops/write_ops_retryability.cpp @@ -34,7 +34,7 @@ #include "mongo/db/dbdirectclient.h" #include "mongo/db/namespace_string.h" #include "mongo/db/ops/find_and_modify_result.h" -#include "mongo/db/query/find_and_modify_request.h" +#include "mongo/db/ops/write_ops_gen.h" #include "mongo/logv2/redaction.h" namespace mongo { @@ -45,7 +45,7 @@ namespace { * In the case of nested oplog entry where the correct links are in the top level * oplog, oplogWithCorrectLinks can be used to specify the outer oplog. */ -void validateFindAndModifyRetryability(const FindAndModifyRequest& request, +void validateFindAndModifyRetryability(const write_ops::FindAndModifyCommand& request, const repl::OplogEntry& oplogEntry, const repl::OplogEntry& oplogWithCorrectLinks) { auto opType = oplogEntry.getOpType(); @@ -58,7 +58,7 @@ void validateFindAndModifyRetryability(const FindAndModifyRequest& request, << " is not compatible with previous write in the transaction of type: " << OpType_serializer(oplogEntry.getOpType()) << ", oplogTs: " << ts.toString() << ", oplog: " << redact(oplogEntry.toBSON()), - request.isRemove()); + request.getRemove().value_or(false)); uassert(40607, str::stream() << "No pre-image available for findAndModify retry request:" << redact(request.toBSON({})), @@ -70,7 +70,7 @@ void validateFindAndModifyRetryability(const FindAndModifyRequest& request, << " is not compatible with previous write in the transaction of type: " << OpType_serializer(oplogEntry.getOpType()) << ", oplogTs: " << ts.toString() << ", oplog: " << redact(oplogEntry.toBSON()), - request.isUpsert()); + request.getUpsert().value_or(false)); } else { uassert( 40609, @@ -80,7 +80,7 @@ void validateFindAndModifyRetryability(const FindAndModifyRequest& request, << ts.toString() << ", oplog: " << redact(oplogEntry.toBSON()), opType == repl::OpTypeEnum::kUpdate); - if (request.shouldReturnNew()) { + if (request.getNew().value_or(false)) { uassert(40611, str::stream() << "findAndModify retry request: " << redact(request.toBSON({})) << " wants the document after update returned, but only before " @@ -127,7 +127,7 @@ BSONObj extractPreOrPostImage(OperationContext* opCtx, const repl::OplogEntry& o * are in the top level oplog, oplogWithCorrectLinks can be used to specify the outer oplog. */ void parseOplogEntryForFindAndModify(OperationContext* opCtx, - const FindAndModifyRequest& request, + const write_ops::FindAndModifyCommand& request, const repl::OplogEntry& oplogEntry, const repl::OplogEntry& oplogWithCorrectLinks, BSONObjBuilder* builder) { @@ -135,12 +135,13 @@ void parseOplogEntryForFindAndModify(OperationContext* opCtx, switch (oplogEntry.getOpType()) { case repl::OpTypeEnum::kInsert: - return find_and_modify::serializeUpsert( - 1, - request.shouldReturnNew() ? oplogEntry.getObject() : boost::optional<BSONObj>(), - false, - oplogEntry.getObject()["_id"], - builder); + return find_and_modify::serializeUpsert(1, + request.getNew().value_or(false) + ? oplogEntry.getObject() + : boost::optional<BSONObj>(), + false, + oplogEntry.getObject()["_id"], + builder); case repl::OpTypeEnum::kUpdate: return find_and_modify::serializeUpsert( 1, extractPreOrPostImage(opCtx, oplogWithCorrectLinks), true, {}, builder); @@ -191,7 +192,7 @@ SingleWriteResult parseOplogEntryForUpdate(const repl::OplogEntry& entry) { } void parseOplogEntryForFindAndModify(OperationContext* opCtx, - const FindAndModifyRequest& request, + const write_ops::FindAndModifyCommand& request, const repl::OplogEntry& oplogEntry, BSONObjBuilder* builder) { // Migrated op case. diff --git a/src/mongo/db/ops/write_ops_retryability.h b/src/mongo/db/ops/write_ops_retryability.h index 1b866dc131c..1f57b19299b 100644 --- a/src/mongo/db/ops/write_ops_retryability.h +++ b/src/mongo/db/ops/write_ops_retryability.h @@ -29,14 +29,13 @@ #pragma once +#include "mongo/db/ops/find_and_modify_command_gen.h" #include "mongo/db/ops/single_write_result_gen.h" -#include "mongo/db/ops/write_ops.h" #include "mongo/db/repl/oplog_entry.h" namespace mongo { class BSONObjBuilder; -class FindAndModifyRequest; class OperationContext; /** @@ -52,7 +51,7 @@ SingleWriteResult parseOplogEntryForUpdate(const repl::OplogEntry& entry); * generated by the operation. */ void parseOplogEntryForFindAndModify(OperationContext* opCtx, - const FindAndModifyRequest& request, + const write_ops::FindAndModifyCommand& request, const repl::OplogEntry& oplogEntry, BSONObjBuilder* builder); diff --git a/src/mongo/db/ops/write_ops_retryability_test.cpp b/src/mongo/db/ops/write_ops_retryability_test.cpp index fcdef0c6845..61738479b60 100644 --- a/src/mongo/db/ops/write_ops_retryability_test.cpp +++ b/src/mongo/db/ops/write_ops_retryability_test.cpp @@ -37,7 +37,6 @@ #include "mongo/db/ops/write_ops_exec.h" #include "mongo/db/ops/write_ops_gen.h" #include "mongo/db/ops/write_ops_retryability.h" -#include "mongo/db/query/find_and_modify_request.h" #include "mongo/db/repl/mock_repl_coord_server_fixture.h" #include "mongo/db/repl/oplog_entry.h" #include "mongo/db/repl/repl_client_info.h" @@ -99,6 +98,16 @@ void setUpTxnParticipant(OperationContext* opCtx, std::vector<int> executedStmtI txnPart.setCommittedStmtIdsForTest(std::move(executedStmtIds)); } +write_ops::FindAndModifyCommand makeFindAndModifyRequest( + NamespaceString fullNs, BSONObj query, boost::optional<write_ops::UpdateModification> update) { + auto request = write_ops::FindAndModifyCommand(fullNs); + request.setQuery(query); + if (update) { + request.setUpdate(std::move(update)); + } + return request; +} + TEST_F(WriteOpsRetryability, ParseOplogEntryForUpdate) { const auto entry = assertGet(repl::OplogEntry::parse( BSON("ts" << Timestamp(50, 10) << "t" << 1LL << "op" @@ -343,7 +352,7 @@ protected: * BSONObjBuilder. */ static BSONObj constructFindAndModifyRetryResult(OperationContext* opCtx, - const FindAndModifyRequest& request, + const write_ops::FindAndModifyCommand& request, const repl::OplogEntry& oplogEntry) { BSONObjBuilder builder; parseOplogEntryForFindAndModify(opCtx, request, oplogEntry, &builder); @@ -354,10 +363,10 @@ protected: const NamespaceString kNs("test.user"); TEST_F(FindAndModifyRetryability, BasicUpsertReturnNew) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); request.setUpsert(true); - request.setShouldReturnNew(true); + request.setNew(true); auto insertOplog = makeOplogEntry(repl::OpTime(), // optime repl::OpTypeEnum::kInsert, // op type @@ -378,10 +387,10 @@ TEST_F(FindAndModifyRetryability, BasicUpsertReturnNew) { } TEST_F(FindAndModifyRetryability, BasicUpsertReturnOld) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); request.setUpsert(true); - request.setShouldReturnNew(false); + request.setNew(false); auto insertOplog = makeOplogEntry(repl::OpTime(), // optime repl::OpTypeEnum::kInsert, // op type @@ -399,10 +408,10 @@ TEST_F(FindAndModifyRetryability, BasicUpsertReturnOld) { } TEST_F(FindAndModifyRetryability, NestedUpsert) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); request.setUpsert(true); - request.setShouldReturnNew(true); + request.setNew(true); auto innerOplog = makeOplogEntry(repl::OpTime(), // optime repl::OpTypeEnum::kInsert, // op type @@ -422,7 +431,7 @@ TEST_F(FindAndModifyRetryability, NestedUpsert) { } TEST_F(FindAndModifyRetryability, AttemptingToRetryUpsertWithUpdateWithoutUpsertErrors) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); request.setUpsert(false); @@ -436,9 +445,9 @@ TEST_F(FindAndModifyRetryability, AttemptingToRetryUpsertWithUpdateWithoutUpsert } TEST_F(FindAndModifyRetryability, ErrorIfRequestIsPostImageButOplogHasPre) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(true); + request.setNew(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -461,9 +470,9 @@ TEST_F(FindAndModifyRetryability, ErrorIfRequestIsPostImageButOplogHasPre) { } TEST_F(FindAndModifyRetryability, ErrorIfRequestIsUpdateButOplogIsDelete) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(true); + request.setNew(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -485,9 +494,9 @@ TEST_F(FindAndModifyRetryability, ErrorIfRequestIsUpdateButOplogIsDelete) { } TEST_F(FindAndModifyRetryability, ErrorIfRequestIsPreImageButOplogHasPost) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(false); + request.setNew(false); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -510,9 +519,9 @@ TEST_F(FindAndModifyRetryability, ErrorIfRequestIsPreImageButOplogHasPost) { } TEST_F(FindAndModifyRetryability, UpdateWithPreImage) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(false); + request.setNew(false); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -537,9 +546,9 @@ TEST_F(FindAndModifyRetryability, UpdateWithPreImage) { } TEST_F(FindAndModifyRetryability, NestedUpdateWithPreImage) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(false); + request.setNew(false); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -570,9 +579,9 @@ TEST_F(FindAndModifyRetryability, NestedUpdateWithPreImage) { } TEST_F(FindAndModifyRetryability, UpdateWithPostImage) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(true); + request.setNew(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -597,9 +606,9 @@ TEST_F(FindAndModifyRetryability, UpdateWithPostImage) { } TEST_F(FindAndModifyRetryability, NestedUpdateWithPostImage) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(true); + request.setNew(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -630,9 +639,9 @@ TEST_F(FindAndModifyRetryability, NestedUpdateWithPostImage) { } TEST_F(FindAndModifyRetryability, UpdateWithPostImageButOplogDoesNotExistShouldError) { - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( kNs, BSONObj(), write_ops::UpdateModification::parseFromClassicUpdate(BSONObj())); - request.setShouldReturnNew(true); + request.setNew(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto updateOplog = makeOplogEntry(repl::OpTime(), // optime @@ -648,7 +657,8 @@ TEST_F(FindAndModifyRetryability, UpdateWithPostImageButOplogDoesNotExistShouldE } TEST_F(FindAndModifyRetryability, BasicRemove) { - auto request = FindAndModifyRequest::makeRemove(kNs, BSONObj()); + auto request = makeFindAndModifyRequest(kNs, BSONObj(), boost::none); + request.setRemove(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -673,7 +683,8 @@ TEST_F(FindAndModifyRetryability, BasicRemove) { } TEST_F(FindAndModifyRetryability, NestedRemove) { - auto request = FindAndModifyRequest::makeRemove(kNs, BSONObj()); + auto request = makeFindAndModifyRequest(kNs, BSONObj(), boost::none); + request.setRemove(true); repl::OpTime imageOpTime(Timestamp(120, 3), 1); auto noteOplog = makeOplogEntry(imageOpTime, // optime @@ -703,7 +714,7 @@ TEST_F(FindAndModifyRetryability, NestedRemove) { } TEST_F(FindAndModifyRetryability, AttemptingToRetryUpsertWithRemoveErrors) { - auto request = FindAndModifyRequest::makeRemove(kNs, BSONObj()); + auto request = makeFindAndModifyRequest(kNs, BSONObj(), boost::none); auto insertOplog = makeOplogEntry(repl::OpTime(), // optime repl::OpTypeEnum::kInsert, // op type diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index 54f0300bf7c..16af82ee571 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -162,7 +162,6 @@ env.Library( "count_request.cpp", 'cursor_request.cpp', 'cursor_response.cpp', - 'find_and_modify_request.cpp', 'getmore_request.cpp', 'killcursors_response.cpp', 'view_response_formatter.cpp', @@ -297,7 +296,6 @@ env.CppUnitTest( "count_command_test.cpp", "cursor_response_test.cpp", "explain_options_test.cpp", - "find_and_modify_request_test.cpp", "get_executor_test.cpp", "getmore_request_test.cpp", "hint_parser_test.cpp", diff --git a/src/mongo/db/query/find_and_modify_request.cpp b/src/mongo/db/query/find_and_modify_request.cpp deleted file mode 100644 index 5662b517286..00000000000 --- a/src/mongo/db/query/find_and_modify_request.cpp +++ /dev/null @@ -1,441 +0,0 @@ -/** - * Copyright (C) 2018-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/query/find_and_modify_request.h" - -#include "mongo/base/status_with.h" -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/bson/util/bson_extract.h" -#include "mongo/db/query/hint_parser.h" -#include "mongo/db/write_concern.h" -#include "mongo/idl/command_generic_argument.h" -#include "mongo/idl/idl_parser.h" - -namespace mongo { - -namespace { -const char kQueryField[] = "query"; -const char kSortField[] = "sort"; -const char kHintField[] = "hint"; -const char kCollationField[] = "collation"; -const char kArrayFiltersField[] = "arrayFilters"; -const char kLegacyRuntimeConstantsField[] = "runtimeConstants"; -const char kLetField[] = "let"; -const char kRemoveField[] = "remove"; -const char kUpdateField[] = "update"; -const char kNewField[] = "new"; -const char kFieldProjectionField[] = "fields"; -const char kUpsertField[] = "upsert"; -const char kWriteConcernField[] = "writeConcern"; -const char kBypassDocumentValidationField[] = "bypassDocumentValidation"; - -const std::vector<BSONObj> emptyArrayFilters{}; - -const std::vector<StringData> _knownFields{kQueryField, - kSortField, - kCollationField, - kArrayFiltersField, - kRemoveField, - kUpdateField, - kNewField, - kFieldProjectionField, - kUpsertField, - kWriteConcernField, - kBypassDocumentValidationField, - FindAndModifyRequest::kLegacyCommandName, - FindAndModifyRequest::kCommandName}; -} // unnamed namespace - -FindAndModifyRequest::FindAndModifyRequest(NamespaceString fullNs, - BSONObj query, - boost::optional<write_ops::UpdateModification> update) - : _ns(std::move(fullNs)), _query(query.getOwned()), _update(std::move(update)) {} - -FindAndModifyRequest FindAndModifyRequest::makeUpdate(NamespaceString fullNs, - BSONObj query, - write_ops::UpdateModification update) { - return FindAndModifyRequest(fullNs, query, std::move(update)); -} - -FindAndModifyRequest FindAndModifyRequest::makeRemove(NamespaceString fullNs, BSONObj query) { - FindAndModifyRequest request(fullNs, query, {}); - return request; -} - -BSONObj FindAndModifyRequest::toBSON(const BSONObj& commandPassthroughFields) const { - BSONObjBuilder builder; - - builder.append(kCommandName, _ns.coll()); - builder.append(kQueryField, _query); - - if (_update) { - _update->serializeToBSON(kUpdateField, &builder); - - if (_isUpsert) { - builder.append(kUpsertField, _isUpsert); - } - } else { - builder.append(kRemoveField, true); - } - - if (_fieldProjection) { - builder.append(kFieldProjectionField, _fieldProjection.get()); - } - - if (_sort) { - builder.append(kSortField, _sort.get()); - } - - if (_hint) { - builder.append(kHintField, _hint.get()); - } - - if (_collation) { - builder.append(kCollationField, _collation.get()); - } - - if (_arrayFilters) { - BSONArrayBuilder arrayBuilder(builder.subarrayStart(kArrayFiltersField)); - for (auto arrayFilter : _arrayFilters.get()) { - arrayBuilder.append(arrayFilter); - } - arrayBuilder.doneFast(); - } - - if (_legacyRuntimeConstants) { - BSONObjBuilder rtcBuilder(builder.subobjStart(kLegacyRuntimeConstantsField)); - _legacyRuntimeConstants->serialize(&rtcBuilder); - rtcBuilder.doneFast(); - } - - if (_letParameters) { - if (auto letParams = _letParameters.get(); !letParams.isEmpty()) { - builder.append(kLetField, _letParameters.get()); - } - } - - if (_shouldReturnNew) { - builder.append(kNewField, _shouldReturnNew); - } - - if (_writeConcern) { - builder.append(kWriteConcernField, _writeConcern->toBSON()); - } - - if (_bypassDocumentValidation) { - builder.append(kBypassDocumentValidationField, _bypassDocumentValidation); - } - - IDLParserErrorContext::appendGenericCommandArguments( - commandPassthroughFields, _knownFields, &builder); - - return builder.obj(); -} - -StatusWith<FindAndModifyRequest> FindAndModifyRequest::parseFromBSON(NamespaceString fullNs, - const BSONObj& cmdObj) { - BSONObj query; - BSONObj fields; - BSONObj updateObj; - BSONObj sort; - BSONObj hint; - boost::optional<write_ops::UpdateModification> update; - - BSONObj collation; - bool shouldReturnNew = false; - bool isUpsert = false; - bool isRemove = false; - bool bypassDocumentValidation = false; - bool arrayFiltersSet = false; - std::vector<BSONObj> arrayFilters; - boost::optional<LegacyRuntimeConstants> runtimeConstants; - BSONObj letParameters; - bool writeConcernOptionsSet = false; - WriteConcernOptions writeConcernOptions; - - for (auto&& field : cmdObj.getFieldNames<std::set<std::string>>()) { - if (field == kQueryField) { - auto queryElement = cmdObj[kQueryField]; - if (queryElement.type() != Object) { - return {ErrorCodes::Error(31160), - str::stream() - << "'" << kQueryField << "' parameter must be an object, found " - << queryElement.type()}; - } - query = queryElement.embeddedObject(); - } else if (field == kSortField) { - auto sortElement = cmdObj[kSortField]; - if (sortElement.type() != Object) { - return {ErrorCodes::Error(31174), - str::stream() - << "'" << kSortField << "' parameter must be an object, found " - << sortElement.type()}; - } - sort = sortElement.embeddedObject(); - } else if (field == kHintField) { - hint = parseHint(cmdObj[kHintField]); - } else if (field == kRemoveField) { - isRemove = cmdObj[kRemoveField].trueValue(); - } else if (field == kUpdateField) { - update = write_ops::UpdateModification::parseFromBSON(cmdObj[kUpdateField]); - } else if (field == kNewField) { - shouldReturnNew = cmdObj[kNewField].trueValue(); - } else if (field == kFieldProjectionField) { - auto projectionElement = cmdObj[kFieldProjectionField]; - if (projectionElement.type() != Object) { - return {ErrorCodes::Error(31175), - str::stream() - << "'" << kFieldProjectionField - << "' parameter must be an object, found " << projectionElement.type()}; - } - fields = projectionElement.embeddedObject(); - } else if (field == kUpsertField) { - isUpsert = cmdObj[kUpsertField].trueValue(); - } else if (field == kBypassDocumentValidationField) { - bypassDocumentValidation = cmdObj[kBypassDocumentValidationField].trueValue(); - } else if (field == kCollationField) { - BSONElement collationElt; - Status collationEltStatus = - bsonExtractTypedField(cmdObj, kCollationField, BSONType::Object, &collationElt); - if (!collationEltStatus.isOK() && (collationEltStatus != ErrorCodes::NoSuchKey)) { - return collationEltStatus; - } - if (collationEltStatus.isOK()) { - collation = collationElt.embeddedObject(); - } - } else if (field == kArrayFiltersField) { - BSONElement arrayFiltersElt; - Status arrayFiltersEltStatus = bsonExtractTypedField( - cmdObj, kArrayFiltersField, BSONType::Array, &arrayFiltersElt); - if (!arrayFiltersEltStatus.isOK() && (arrayFiltersEltStatus != ErrorCodes::NoSuchKey)) { - return arrayFiltersEltStatus; - } - if (arrayFiltersEltStatus.isOK()) { - arrayFiltersSet = true; - for (auto arrayFilter : arrayFiltersElt.embeddedObject()) { - if (arrayFilter.type() != BSONType::Object) { - return {ErrorCodes::TypeMismatch, - str::stream() << "Each array filter must be an object, found " - << arrayFilter.type()}; - } - arrayFilters.push_back(arrayFilter.embeddedObject()); - } - } - } else if (field == kLegacyRuntimeConstantsField) { - runtimeConstants = - LegacyRuntimeConstants::parse(IDLParserErrorContext(kLegacyRuntimeConstantsField), - cmdObj.getObjectField(kLegacyRuntimeConstantsField)); - } else if (field == kLetField) { - BSONElement letElt; - if (Status letEltStatus = - bsonExtractTypedField(cmdObj, kLetField, BSONType::Object, &letElt); - !letEltStatus.isOK()) { - return letEltStatus; - } - letParameters = letElt.embeddedObject(); - } else if (field == kWriteConcernField) { - BSONElement writeConcernElt; - Status writeConcernEltStatus = bsonExtractTypedField( - cmdObj, kWriteConcernField, BSONType::Object, &writeConcernElt); - if (!writeConcernEltStatus.isOK()) { - return writeConcernEltStatus; - } - auto sw = WriteConcernOptions::parse(writeConcernElt.embeddedObject()); - if (!sw.isOK()) { - return sw.getStatus(); - } else { - writeConcernOptionsSet = true; - writeConcernOptions = sw.getValue(); - } - } else if (isMongocryptdArgument(field)) { - return {ErrorCodes::FailedToParse, - str::stream() << "unrecognized field '" << field - << "'. This command may be meant for a mongocryptd process."}; - } else if (!isGenericArgument(field) && - !std::count(_knownFields.begin(), _knownFields.end(), field)) { - return {ErrorCodes::Error(51177), - str::stream() << "BSON field '" << field << "' is an unknown field."}; - } - } - - if (!isRemove && !update) { - return {ErrorCodes::FailedToParse, "Either an update or remove=true must be specified"}; - } - - if (isRemove) { - if (update) { - return {ErrorCodes::FailedToParse, "Cannot specify both an update and remove=true"}; - } - - if (isUpsert) { - return {ErrorCodes::FailedToParse, "Cannot specify both upsert=true and remove=true"}; - } - - if (shouldReturnNew) { - return {ErrorCodes::FailedToParse, - "Cannot specify both new=true and remove=true;" - " 'remove' always returns the deleted document"}; - } - - if (arrayFiltersSet) { - return {ErrorCodes::FailedToParse, "Cannot specify arrayFilters and remove=true"}; - } - } - - if (update && update->type() == write_ops::UpdateModification::Type::kPipeline && - arrayFiltersSet) { - return {ErrorCodes::FailedToParse, "Cannot specify arrayFilters and a pipeline update"}; - } - - FindAndModifyRequest request(std::move(fullNs), query, std::move(update)); - request.setFieldProjection(fields); - request.setSort(sort); - request.setHint(hint); - request.setCollation(collation); - request.setBypassDocumentValidation(bypassDocumentValidation); - request.setLetParameters(letParameters); - if (arrayFiltersSet) { - request.setArrayFilters(std::move(arrayFilters)); - } - if (runtimeConstants) { - request.setLegacyRuntimeConstants(*runtimeConstants); - } - if (writeConcernOptionsSet) { - request.setWriteConcern(std::move(writeConcernOptions)); - } - - if (!isRemove) { - request.setShouldReturnNew(shouldReturnNew); - request.setUpsert(isUpsert); - } - - return request; -} - -void FindAndModifyRequest::setFieldProjection(BSONObj fields) { - _fieldProjection = fields.getOwned(); -} - -void FindAndModifyRequest::setSort(BSONObj sort) { - _sort = sort.getOwned(); -} - -void FindAndModifyRequest::setHint(BSONObj hint) { - _hint = hint.getOwned(); -} - -void FindAndModifyRequest::setCollation(BSONObj collation) { - _collation = collation.getOwned(); -} - -void FindAndModifyRequest::setArrayFilters(const std::vector<BSONObj>& arrayFilters) { - _arrayFilters = std::vector<BSONObj>(); - for (auto arrayFilter : arrayFilters) { - _arrayFilters->emplace_back(arrayFilter.getOwned()); - } -} - -void FindAndModifyRequest::setQuery(BSONObj query) { - _query = query.getOwned(); -} -void FindAndModifyRequest::setUpdateObj(BSONObj updateObj) { - _update.emplace(write_ops::UpdateModification::parseFromClassicUpdate(updateObj.getOwned())); -} - -void FindAndModifyRequest::setShouldReturnNew(bool shouldReturnNew) { - dassert(_update); - _shouldReturnNew = shouldReturnNew; -} - -void FindAndModifyRequest::setUpsert(bool upsert) { - dassert(_update); - _isUpsert = upsert; -} - -void FindAndModifyRequest::setWriteConcern(WriteConcernOptions writeConcern) { - _writeConcern = std::move(writeConcern); -} - -void FindAndModifyRequest::setBypassDocumentValidation(bool bypassDocumentValidation) { - _bypassDocumentValidation = bypassDocumentValidation; -} - -const NamespaceString& FindAndModifyRequest::getNamespaceString() const { - return _ns; -} - -BSONObj FindAndModifyRequest::getQuery() const { - return _query; -} - -BSONObj FindAndModifyRequest::getFields() const { - return _fieldProjection.value_or(BSONObj()); -} - -const boost::optional<write_ops::UpdateModification>& FindAndModifyRequest::getUpdate() const { - return _update; -} - -BSONObj FindAndModifyRequest::getSort() const { - return _sort.value_or(BSONObj()); -} - -BSONObj FindAndModifyRequest::getHint() const { - return _hint.value_or(BSONObj()); -} - -BSONObj FindAndModifyRequest::getCollation() const { - return _collation.value_or(BSONObj()); -} - -const std::vector<BSONObj>& FindAndModifyRequest::getArrayFilters() const { - if (_arrayFilters) { - return _arrayFilters.get(); - } - return emptyArrayFilters; -} - -bool FindAndModifyRequest::shouldReturnNew() const { - return _shouldReturnNew; -} - -bool FindAndModifyRequest::isUpsert() const { - return _isUpsert; -} - -bool FindAndModifyRequest::isRemove() const { - return !static_cast<bool>(_update); -} - -bool FindAndModifyRequest::getBypassDocumentValidation() const { - return _bypassDocumentValidation; -} -} // namespace mongo diff --git a/src/mongo/db/query/find_and_modify_request.h b/src/mongo/db/query/find_and_modify_request.h deleted file mode 100644 index 2f622d0f9bf..00000000000 --- a/src/mongo/db/query/find_and_modify_request.h +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright (C) 2018-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 <boost/optional.hpp> - -#include "mongo/db/jsobj.h" -#include "mongo/db/namespace_string.h" -#include "mongo/db/ops/write_ops_parsers.h" -#include "mongo/db/pipeline/legacy_runtime_constants_gen.h" -#include "mongo/db/write_concern_options.h" - -namespace mongo { - -template <typename T> -class StatusWith; - -/** - * Represents the user-supplied options to the findAndModify command. Note that this - * does not offer round trip preservation. For example, for the case where - * output = parseBSON(input).toBSON(), 'output' is not guaranteed to be equal to 'input'. - * However, the semantic meaning of 'output' will be the same with 'input'. - * - * The BSONObj members contained within this struct are owned objects. - */ -class FindAndModifyRequest { -public: - static constexpr auto kLegacyCommandName = "findandmodify"_sd; - static constexpr auto kCommandName = "findAndModify"_sd; - - /** - * Creates a new instance of a 'update' type findAndModify request. - */ - static FindAndModifyRequest makeUpdate(NamespaceString fullNs, - BSONObj query, - write_ops::UpdateModification update); - - /** - * Creates a new instance of an 'remove' type findAndModify request. - */ - static FindAndModifyRequest makeRemove(NamespaceString fullNs, BSONObj query); - - /** - * Create a new instance of FindAndModifyRequest from a valid BSONObj. - * Returns an error if the BSONObj is malformed. - * Format: - * - * { - * findAndModify: <collection-name>, - * query: <document>, - * sort: <document>, - * collation: <document>, - * arrayFilters: <array>, - * remove: <boolean>, - * update: <document>, - * new: <boolean>, - * fields: <document>, - * upsert: <boolean> - * } - * - * Note: does not parse the writeConcern field or the findAndModify field. - */ - static StatusWith<FindAndModifyRequest> parseFromBSON(NamespaceString fullNs, - const BSONObj& cmdObj); - - /** - * Serializes this object into a BSON representation. Fields that are not - * set will not be part of the the serialized object. Passthrough fields - * are appended. - */ - BSONObj toBSON(const BSONObj& commandPassthroughFields) const; - - const NamespaceString& getNamespaceString() const; - BSONObj getQuery() const; - BSONObj getFields() const; - const boost::optional<write_ops::UpdateModification>& getUpdate() const; - BSONObj getSort() const; - BSONObj getHint() const; - BSONObj getCollation() const; - const std::vector<BSONObj>& getArrayFilters() const; - bool shouldReturnNew() const; - bool isUpsert() const; - bool isRemove() const; - bool getBypassDocumentValidation() const; - - // Not implemented. Use extractWriteConcern() to get the setting instead. - WriteConcernOptions getWriteConcern() const; - - // - // Setters for update type request only. - // - - /** - * Sets the filter to find a document. - */ - void setQuery(BSONObj query); - - /** - * Sets the update object that specifies how a document gets updated. - */ - void setUpdateObj(BSONObj updateObj); - - /** - * If shouldReturnNew is new, the findAndModify response should return the document - * after the modification was applied if the query matched a document. Otherwise, - * it will return the matched document before the modification. - */ - void setShouldReturnNew(bool shouldReturnNew); - - /** - * Sets a flag whether the statement performs an upsert. - */ - void setUpsert(bool upsert); - - // - // Setters for optional parameters - // - - /** - * Specifies the field to project on the matched document. - */ - void setFieldProjection(BSONObj fields); - - /** - * Sets the sort order for the query. In cases where the query yields multiple matches, - * only the first document based on the sort order will be modified/removed. - */ - void setSort(BSONObj sort); - - /** - * Sets a hint on the query, which will force the planner to use the specified index. - */ - void setHint(BSONObj hint); - - /** - * Sets the collation for the query, which is used for all string comparisons. - */ - void setCollation(BSONObj collation); - - /** - * Sets the array filters for the update, which determine which array elements should be - * modified. - */ - void setArrayFilters(const std::vector<BSONObj>& arrayFilters); - - /** - * Sets any constant values which may be required by the query and/or update. - */ - void setLegacyRuntimeConstants(LegacyRuntimeConstants runtimeConstants) { - _legacyRuntimeConstants = std::move(runtimeConstants); - } - - /** - * Returns the runtime constants associated with this findAndModify request, if present. - */ - const boost::optional<LegacyRuntimeConstants>& getLegacyRuntimeConstants() const { - return _legacyRuntimeConstants; - } - - /** - * Sets user-specified parameters that can be used by the findAndModify command's pipeline-style - * updates and inside $expr. - */ - void setLetParameters(const boost::optional<BSONObj>& letParameters) { - _letParameters = letParameters; - } - - /** - * Returns the runtime constants associated with this findAndModify request, if present. - */ - const boost::optional<BSONObj>& getLetParameters() const { - return _letParameters; - } - - /** - * Sets the write concern for this request. - */ - void setWriteConcern(WriteConcernOptions writeConcern); - - void setBypassDocumentValidation(bool bypassDocumentValidation); - -private: - /** - * Creates a new FindAndModifyRequest with the required fields. - */ - FindAndModifyRequest(NamespaceString fullNs, - BSONObj query, - boost::optional<write_ops::UpdateModification> update); - - // Required fields - const NamespaceString _ns; - BSONObj _query; - - bool _isUpsert{false}; - boost::optional<BSONObj> _fieldProjection; - boost::optional<BSONObj> _sort; - boost::optional<BSONObj> _hint; - boost::optional<BSONObj> _collation; - boost::optional<std::vector<BSONObj>> _arrayFilters; - boost::optional<LegacyRuntimeConstants> _legacyRuntimeConstants; - boost::optional<BSONObj> _letParameters; - bool _shouldReturnNew{false}; - boost::optional<WriteConcernOptions> _writeConcern; - bool _bypassDocumentValidation{false}; - - // Holds value when performing an update request and none when a remove request. - boost::optional<write_ops::UpdateModification> _update; -}; -} // namespace mongo diff --git a/src/mongo/db/query/find_and_modify_request_test.cpp b/src/mongo/db/query/find_and_modify_request_test.cpp deleted file mode 100644 index 5f4e4f64145..00000000000 --- a/src/mongo/db/query/find_and_modify_request_test.cpp +++ /dev/null @@ -1,786 +0,0 @@ -/** - * Copyright (C) 2018-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/bson/json.h" -#include "mongo/db/commands/test_commands_enabled.h" -#include "mongo/db/pipeline/legacy_runtime_constants_gen.h" -#include "mongo/db/query/find_and_modify_request.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { -namespace { - -TEST(FindAndModifyRequest, BasicUpdate) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, PipelineUpdate) { - const BSONObj query(BSON("x" << 1)); - const BSONObj pipelineBSON( - BSON("pipeline" << BSON_ARRAY(BSON("$addFields" << BSON("y" << 1))))); - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), query, pipelineBSON["pipeline"]); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: [{$addFields: {y: 1}}] - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithUpsert) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setUpsert(true); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - upsert: true - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithUpsertFalse) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setUpsert(false); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithProjection) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const BSONObj field(BSON("z" << 1)); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setFieldProjection(field); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - fields: { z: 1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithNewTrue) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setShouldReturnNew(true); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - new: true - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithNewFalse) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setShouldReturnNew(false); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithSort) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const BSONObj sort(BSON("z" << -1)); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setSort(sort); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - sort: { z: -1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithHint) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const BSONObj hint(BSON("z" << -1)); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setHint(hint); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - hint: { z: -1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithCollation) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const BSONObj collation(BSON("locale" - << "en_US")); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setCollation(collation); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - collation: { locale: 'en_US' } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithArrayFilters) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const std::vector<BSONObj> arrayFilters{BSON("i" << 0)}; - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setArrayFilters(arrayFilters); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - arrayFilters: [ { i: 0 } ] - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithWriteConcern) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const WriteConcernOptions writeConcern(2, WriteConcernOptions::SyncMode::FSYNC, 150); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setWriteConcern(writeConcern); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - writeConcern: { w: 2, fsync: true, wtimeout: 150 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithLegacyRuntimeConstants) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setLegacyRuntimeConstants({Date_t(), Timestamp(1, 0)}); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - runtimeConstants: { - localNow: new Date(0), - clusterTime: Timestamp(1, 0) - } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, UpdateWithFullSpec) { - const BSONObj query(BSON("x" << 1)); - const BSONObj update(BSON("y" << 1)); - const BSONObj sort(BSON("z" << -1)); - const BSONObj hint(BSON("z" << -1)); - const BSONObj collation(BSON("locale" - << "en_US")); - const std::vector<BSONObj> arrayFilters{BSON("i" << 0)}; - const BSONObj field(BSON("x" << 1 << "y" << 1)); - const WriteConcernOptions writeConcern(2, WriteConcernOptions::SyncMode::FSYNC, 150); - auto rtc = LegacyRuntimeConstants{Date_t(), Timestamp(1, 0)}; - - auto request = FindAndModifyRequest::makeUpdate( - NamespaceString("test.user"), - query, - write_ops::UpdateModification::parseFromClassicUpdate(update)); - request.setFieldProjection(field); - request.setShouldReturnNew(true); - request.setSort(sort); - request.setHint(hint); - request.setCollation(collation); - request.setArrayFilters(arrayFilters); - request.setLegacyRuntimeConstants(rtc); - request.setWriteConcern(writeConcern); - request.setBypassDocumentValidation(true); - request.setUpsert(true); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - upsert: true, - fields: { x: 1, y: 1 }, - sort: { z: -1 }, - hint: { z: -1 }, - collation: { locale: 'en_US' }, - arrayFilters: [ { i: 0 } ], - runtimeConstants: { - localNow: new Date(0), - clusterTime: Timestamp(1, 0) - }, - new: true, - writeConcern: { w: 2, fsync: true, wtimeout: 150 }, - bypassDocumentValidation: true - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, BasicRemove) { - const BSONObj query(BSON("x" << 1)); - auto request = FindAndModifyRequest::makeRemove(NamespaceString("test.user"), query); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, RemoveWithProjection) { - const BSONObj query(BSON("x" << 1)); - const BSONObj field(BSON("z" << 1)); - - auto request = FindAndModifyRequest::makeRemove(NamespaceString("test.user"), query); - request.setFieldProjection(field); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - fields: { z: 1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, RemoveWithSort) { - const BSONObj query(BSON("x" << 1)); - const BSONObj sort(BSON("z" << -1)); - - auto request = FindAndModifyRequest::makeRemove(NamespaceString("test.user"), query); - request.setSort(sort); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - sort: { z: -1 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, RemoveWithCollation) { - const BSONObj query(BSON("x" << 1)); - const BSONObj collation(BSON("locale" - << "en_US")); - - auto request = FindAndModifyRequest::makeRemove(NamespaceString("test.user"), query); - request.setCollation(collation); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - collation: { locale: 'en_US' } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, RemoveWithWriteConcern) { - const BSONObj query(BSON("x" << 1)); - const WriteConcernOptions writeConcern(2, WriteConcernOptions::SyncMode::FSYNC, 150); - - auto request = FindAndModifyRequest::makeRemove(NamespaceString("test.user"), query); - request.setWriteConcern(writeConcern); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - writeConcern: { w: 2, fsync: true, wtimeout: 150 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, RemoveWithFullSpec) { - const BSONObj query(BSON("x" << 1)); - const BSONObj sort(BSON("z" << -1)); - const BSONObj collation(BSON("locale" - << "en_US")); - const BSONObj field(BSON("x" << 1 << "y" << 1)); - const WriteConcernOptions writeConcern(2, WriteConcernOptions::SyncMode::FSYNC, 150); - auto rtc = LegacyRuntimeConstants{Date_t(), Timestamp(1, 0)}; - - auto request = FindAndModifyRequest::makeRemove(NamespaceString("test.user"), query); - request.setFieldProjection(field); - request.setSort(sort); - request.setCollation(collation); - request.setWriteConcern(writeConcern); - request.setLegacyRuntimeConstants(rtc); - - BSONObj expectedObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - fields: { x: 1, y: 1 }, - sort: { z: -1 }, - collation: { locale: 'en_US' }, - runtimeConstants: { - localNow: new Date(0), - clusterTime: Timestamp(1, 0) - }, - writeConcern: { w: 2, fsync: true, wtimeout: 150 } - })json")); - - ASSERT_BSONOBJ_EQ(expectedObj, request.toBSON({})); -} - -TEST(FindAndModifyRequest, ParseWithUpdateOnlyRequiredFields) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - update: { y: 1 } - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_OK(parseStatus.getStatus()); - - auto request = parseStatus.getValue(); - ASSERT_EQ(NamespaceString("a.b"), request.getNamespaceString()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1), request.getQuery()); - ASSERT(request.getUpdate()); - ASSERT(request.getUpdate()->type() == write_ops::UpdateModification::Type::kClassic); - ASSERT_BSONOBJ_EQ(BSON("y" << 1), request.getUpdate()->getUpdateClassic()); - ASSERT_EQ(false, request.isUpsert()); - ASSERT_EQ(false, request.isRemove()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getFields()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getSort()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getHint()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getCollation()); - ASSERT_EQ(0u, request.getArrayFilters().size()); - ASSERT_EQ(false, request.shouldReturnNew()); -} - -TEST(FindAndModifyRequest, ParseWithUpdateFullSpec) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - update: { y: 1 }, - upsert: true, - fields: { x: 1, y: 1 }, - sort: { z: -1 }, - hint: { z: -1 }, - collation: {locale: 'en_US' }, - arrayFilters: [ { i: 0 } ], - new: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_OK(parseStatus.getStatus()); - - auto request = parseStatus.getValue(); - ASSERT_EQ(NamespaceString("a.b"), request.getNamespaceString()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1), request.getQuery()); - ASSERT(request.getUpdate()); - ASSERT(request.getUpdate()->type() == write_ops::UpdateModification::Type::kClassic); - ASSERT_BSONOBJ_EQ(BSON("y" << 1), request.getUpdate()->getUpdateClassic()); - ASSERT_EQ(true, request.isUpsert()); - ASSERT_EQ(false, request.isRemove()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1 << "y" << 1), request.getFields()); - ASSERT_BSONOBJ_EQ(BSON("z" << -1), request.getSort()); - ASSERT_BSONOBJ_EQ(BSON("z" << -1), request.getHint()); - ASSERT_BSONOBJ_EQ(BSON("locale" - << "en_US"), - request.getCollation()); - ASSERT_EQ(1u, request.getArrayFilters().size()); - ASSERT_BSONOBJ_EQ(BSON("i" << 0), request.getArrayFilters()[0]); - ASSERT_EQ(true, request.shouldReturnNew()); -} - -TEST(FindAndModifyRequest, ParseWithUpdateAndStringHint) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - update: { y: 1 }, - hint: 'z_1' - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_OK(parseStatus.getStatus()); - - auto request = parseStatus.getValue(); - ASSERT_EQ(NamespaceString("a.b"), request.getNamespaceString()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1), request.getQuery()); - ASSERT(request.getUpdate()); - ASSERT(request.getUpdate()->type() == write_ops::UpdateModification::Type::kClassic); - ASSERT_BSONOBJ_EQ(BSON("y" << 1), request.getUpdate()->getUpdateClassic()); - ASSERT_BSONOBJ_EQ(BSON("$hint" - << "z_1"), - request.getHint()); - ASSERT_EQ(false, request.isUpsert()); - ASSERT_EQ(false, request.isRemove()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getFields()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getSort()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getCollation()); - ASSERT_EQ(0u, request.getArrayFilters().size()); - ASSERT_EQ(false, request.shouldReturnNew()); -} - -TEST(FindAndModifyRequest, ParseWithRemoveOnlyRequiredFields) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - remove: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_OK(parseStatus.getStatus()); - - auto request = parseStatus.getValue(); - ASSERT_EQ(NamespaceString("a.b"), request.getNamespaceString()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1), request.getQuery()); - ASSERT_FALSE(request.getUpdate()); - ASSERT_EQ(false, request.isUpsert()); - ASSERT_EQ(true, request.isRemove()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getFields()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getSort()); - ASSERT_BSONOBJ_EQ(BSONObj(), request.getCollation()); - ASSERT_EQ(false, request.shouldReturnNew()); -} - -TEST(FindAndModifyRequest, ParseWithRemoveFullSpec) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - remove: true, - fields: { x: 1, y: 1 }, - sort: { z: -1 }, - collation: { locale: 'en_US' }, - new: false - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_OK(parseStatus.getStatus()); - - auto request = parseStatus.getValue(); - ASSERT_EQ(NamespaceString("a.b"), request.getNamespaceString()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1), request.getQuery()); - ASSERT_FALSE(request.getUpdate()); - ASSERT_EQ(false, request.isUpsert()); - ASSERT_EQ(true, request.isRemove()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1 << "y" << 1), request.getFields()); - ASSERT_BSONOBJ_EQ(BSON("z" << -1), request.getSort()); - ASSERT_BSONOBJ_EQ(BSON("locale" - << "en_US"), - request.getCollation()); - ASSERT_EQ(false, request.shouldReturnNew()); -} - -TEST(FindAndModifyRequest, ParseWithIncompleteSpec) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 } - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_NOT_OK(parseStatus.getStatus()); -} - -TEST(FindAndModifyRequest, ParseWithAmbiguousUpdateRemove) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - update: { y: 1 }, - remove: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_NOT_OK(parseStatus.getStatus()); -} - -TEST(FindAndModifyRequest, ParseWithRemovePlusUpsert) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - upsert: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_NOT_OK(parseStatus.getStatus()); -} - -TEST(FindAndModifyRequest, ParseWithRemoveAndReturnNew) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - new: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_NOT_OK(parseStatus.getStatus()); -} - -TEST(FindAndModifyRequest, ParseWithRemoveAndArrayFilters) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - arrayFilters: [ { i: 0 } ] - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_NOT_OK(parseStatus.getStatus()); -} - -TEST(FindAndModifyRequest, ParseWithCollationTypeMismatch) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - update: { y: 1 }, - collation: 'en_US' - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(parseStatus.getStatus(), ErrorCodes::TypeMismatch); -} - -TEST(FindAndModifyRequest, ParseWithBypassDocumentValidation) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - bypassDocumentValidation: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_OK(parseStatus.getStatus()); - - auto request = parseStatus.getValue(); - ASSERT_EQ(NamespaceString("a.b"), request.getNamespaceString()); - ASSERT_BSONOBJ_EQ(BSON("x" << 1), request.getQuery()); - ASSERT_FALSE(request.getUpdate()); - ASSERT_EQ(true, request.isRemove()); - ASSERT_EQ(true, request.getBypassDocumentValidation()); -} - -TEST(FindAndModifyRequest, ParseWithWriteConcernAsArray) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: { x: 1 }, - remove: true, - writeConcern: [] - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(ErrorCodes::TypeMismatch, parseStatus.getStatus()); -} - -TEST(FindAndModifyRequest, ParsesAndSerializesPipelineUpdate) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - update: [{$replaceWith: {y: 1}}] - })json")); - - auto request = - unittest::assertGet(FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj)); - ASSERT(request.getUpdate()); - ASSERT(request.getUpdate()->type() == write_ops::UpdateModification::Type::kPipeline); - auto serialized = request.toBSON({}); - ASSERT_BSONOBJ_EQ(serialized, fromjson(R"json({ - findAndModify: "b", - query: {x: 1}, - update: [{$replaceWith: {y: 1}}], - fields: {}, - sort: {}, - hint: {}, - collation: {} - })json")); - ASSERT_OK(FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), serialized).getStatus()); -} - -TEST(FindAndModifyRequest, RejectsBothArrayFiltersAndPipelineUpdate) { - BSONObj cmdObj(fromjson(R"json({ - query: { x: 1 }, - update: [{$replaceWith: {y: 1}}], - arrayFilters: [] - })json")); - - auto swRequestNoFilters = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(swRequestNoFilters.getStatus(), ErrorCodes::FailedToParse); - - cmdObj = fromjson(R"json({ - query: { x: 1 }, - update: [{$replaceWith: {y: 1}}], - arrayFilters: [{"i.x": 1}] - })json"); - auto swRequestOneFilter = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(swRequestOneFilter.getStatus(), ErrorCodes::FailedToParse); -} - -TEST(FindAndModifyRequest, InvalidQueryParameter) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - query: '{ x: 1 }', - remove: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(31160, parseStatus.getStatus().code()); -} - -TEST(FindAndModifyRequest, InvalidSortParameter) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - sort: 1, - remove: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(31174, parseStatus.getStatus().code()); -} - -TEST(FindAndModifyRequest, InvalidHintParameter) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - hint: 1 - })json")); - - ASSERT_THROWS_CODE(FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj), - AssertionException, - ErrorCodes::FailedToParse); -} - -TEST(FindAndModifyRequest, InvalidFieldParameter) { - BSONObj cmdObj(fromjson(R"json({ - findAndModify: 'user', - fields: null, - remove: true - })json")); - - auto parseStatus = FindAndModifyRequest::parseFromBSON(NamespaceString("a.b"), cmdObj); - ASSERT_EQ(31175, parseStatus.getStatus().code()); -} -} // unnamed namespace -} // namespace mongo diff --git a/src/mongo/db/s/shard_local_test.cpp b/src/mongo/db/s/shard_local_test.cpp index 200043c20df..48d3b61533d 100644 --- a/src/mongo/db/s/shard_local_test.cpp +++ b/src/mongo/db/s/shard_local_test.cpp @@ -33,8 +33,8 @@ #include "mongo/db/catalog_raii.h" #include "mongo/db/client.h" #include "mongo/db/dbdirectclient.h" +#include "mongo/db/ops/find_and_modify_command_gen.h" #include "mongo/db/query/cursor_response.h" -#include "mongo/db/query/find_and_modify_request.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/replication_coordinator_mock.h" #include "mongo/db/s/shard_local.h" @@ -101,12 +101,15 @@ void ShardLocalTest::tearDown() { StatusWith<Shard::CommandResponse> ShardLocalTest::runFindAndModifyRunCommand(NamespaceString nss, BSONObj find, BSONObj set) { - FindAndModifyRequest findAndModifyRequest = FindAndModifyRequest::makeUpdate( - nss, find, write_ops::UpdateModification::parseFromClassicUpdate(set)); + auto findAndModifyRequest = write_ops::FindAndModifyCommand(nss); + findAndModifyRequest.setQuery(find); + findAndModifyRequest.setUpdate(write_ops::UpdateModification::parseFromClassicUpdate(set)); findAndModifyRequest.setUpsert(true); - findAndModifyRequest.setShouldReturnNew(true); - findAndModifyRequest.setWriteConcern(WriteConcernOptions( - WriteConcernOptions::kMajority, WriteConcernOptions::SyncMode::UNSET, Seconds(15))); + findAndModifyRequest.setNew(true); + findAndModifyRequest.setWriteConcern(WriteConcernOptions(WriteConcernOptions::kMajority, + WriteConcernOptions::SyncMode::UNSET, + Seconds(15)) + .toBSON()); return _shardLocal->runCommandWithFixedRetryAttempts( _opCtx.get(), diff --git a/src/mongo/s/catalog/dist_lock_catalog_impl.cpp b/src/mongo/s/catalog/dist_lock_catalog_impl.cpp index 56b925be417..9dc62b9d360 100644 --- a/src/mongo/s/catalog/dist_lock_catalog_impl.cpp +++ b/src/mongo/s/catalog/dist_lock_catalog_impl.cpp @@ -38,7 +38,6 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/client/read_preference.h" #include "mongo/db/lasterror.h" -#include "mongo/db/query/find_and_modify_request.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/rpc/metadata.h" @@ -156,6 +155,18 @@ StatusWith<OID> extractElectionId(const BSONObj& responseObj) { return electionId; } +write_ops::FindAndModifyCommand makeFindAndModifyRequest( + NamespaceString fullNs, BSONObj query, boost::optional<write_ops::UpdateModification> update) { + auto request = write_ops::FindAndModifyCommand(fullNs); + request.setQuery(query); + if (update) { + request.setUpdate(std::move(update)); + } else { + request.setRemove(true); + } + return request; +} + } // unnamed namespace DistLockCatalogImpl::DistLockCatalogImpl() @@ -191,13 +202,12 @@ StatusWith<LockpingsType> DistLockCatalogImpl::getPing(OperationContext* opCtx, } Status DistLockCatalogImpl::ping(OperationContext* opCtx, StringData processID, Date_t ping) { - auto request = - FindAndModifyRequest::makeUpdate(_lockPingNS, - BSON(LockpingsType::process() << processID), - write_ops::UpdateModification::parseFromClassicUpdate( - BSON("$set" << BSON(LockpingsType::ping(ping))))); + auto request = write_ops::FindAndModifyCommand(_lockPingNS); + request.setQuery(BSON(LockpingsType::process() << processID)); + request.setUpdate(write_ops::UpdateModification::parseFromClassicUpdate( + BSON("$set" << BSON(LockpingsType::ping(ping))))); request.setUpsert(true); - request.setWriteConcern(kMajorityWriteConcern); + request.setWriteConcern(kMajorityWriteConcern.toBSON()); auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); auto resultStatus = shardRegistry->getConfigShard()->runCommandWithFixedRetryAttempts( @@ -225,13 +235,13 @@ StatusWith<LocksType> DistLockCatalogImpl::grabLock(OperationContext* opCtx, << LocksType::process() << processId << LocksType::when(time) << LocksType::why() << why)); - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( _locksNS, BSON(LocksType::name() << lockID << LocksType::state(LocksType::UNLOCKED)), write_ops::UpdateModification::parseFromClassicUpdate(BSON("$set" << newLockDetails))); request.setUpsert(true); - request.setShouldReturnNew(true); - request.setWriteConcern(writeConcern); + request.setNew(true); + request.setWriteConcern(writeConcern.toBSON()); auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); auto resultStatus = shardRegistry->getConfigShard()->runCommandWithFixedRetryAttempts( @@ -282,12 +292,12 @@ StatusWith<LocksType> DistLockCatalogImpl::overtakeLock(OperationContext* opCtx, << LocksType::process() << processId << LocksType::when(time) << LocksType::why() << why)); - auto request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( _locksNS, BSON("$or" << orQueryBuilder.arr()), write_ops::UpdateModification::parseFromClassicUpdate(BSON("$set" << newLockDetails))); - request.setShouldReturnNew(true); - request.setWriteConcern(kMajorityWriteConcern); + request.setNew(true); + request.setWriteConcern(kMajorityWriteConcern.toBSON()); auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); auto resultStatus = shardRegistry->getConfigShard()->runCommandWithFixedRetryAttempts( @@ -315,28 +325,29 @@ StatusWith<LocksType> DistLockCatalogImpl::overtakeLock(OperationContext* opCtx, } Status DistLockCatalogImpl::unlock(OperationContext* opCtx, const OID& lockSessionID) { - FindAndModifyRequest request = FindAndModifyRequest::makeUpdate( - _locksNS, - BSON(LocksType::lockID(lockSessionID)), - write_ops::UpdateModification::parseFromClassicUpdate( - BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))))); - request.setWriteConcern(kMajorityWriteConcern); + auto request = + makeFindAndModifyRequest(_locksNS, + BSON(LocksType::lockID(lockSessionID)), + write_ops::UpdateModification::parseFromClassicUpdate( + BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))))); + request.setWriteConcern(kMajorityWriteConcern.toBSON()); return _unlock(opCtx, request); } Status DistLockCatalogImpl::unlock(OperationContext* opCtx, const OID& lockSessionID, StringData name) { - FindAndModifyRequest request = FindAndModifyRequest::makeUpdate( + auto request = makeFindAndModifyRequest( _locksNS, BSON(LocksType::lockID(lockSessionID) << LocksType::name(name.toString())), write_ops::UpdateModification::parseFromClassicUpdate( BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))))); - request.setWriteConcern(kMajorityWriteConcern); + request.setWriteConcern(kMajorityWriteConcern.toBSON()); return _unlock(opCtx, request); } -Status DistLockCatalogImpl::_unlock(OperationContext* opCtx, const FindAndModifyRequest& request) { +Status DistLockCatalogImpl::_unlock(OperationContext* opCtx, + const write_ops::FindAndModifyCommand& request) { auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); auto resultStatus = shardRegistry->getConfigShard()->runCommandWithFixedRetryAttempts( opCtx, @@ -498,8 +509,8 @@ StatusWith<LocksType> DistLockCatalogImpl::getLockByName(OperationContext* opCtx Status DistLockCatalogImpl::stopPing(OperationContext* opCtx, StringData processId) { auto request = - FindAndModifyRequest::makeRemove(_lockPingNS, BSON(LockpingsType::process() << processId)); - request.setWriteConcern(kMajorityWriteConcern); + makeFindAndModifyRequest(_lockPingNS, BSON(LockpingsType::process() << processId), {}); + request.setWriteConcern(kMajorityWriteConcern.toBSON()); auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); auto resultStatus = shardRegistry->getConfigShard()->runCommandWithFixedRetryAttempts( diff --git a/src/mongo/s/catalog/dist_lock_catalog_impl.h b/src/mongo/s/catalog/dist_lock_catalog_impl.h index 6735576002e..cdbb4bff12c 100644 --- a/src/mongo/s/catalog/dist_lock_catalog_impl.h +++ b/src/mongo/s/catalog/dist_lock_catalog_impl.h @@ -35,13 +35,13 @@ #include "mongo/bson/oid.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/ops/find_and_modify_command_gen.h" #include "mongo/db/write_concern_options.h" #include "mongo/s/catalog/dist_lock_catalog.h" #include "mongo/util/time_support.h" namespace mongo { -class FindAndModifyRequest; struct ReadPreferenceSetting; class DistLockCatalogImpl final : public DistLockCatalog { @@ -86,7 +86,7 @@ public: Status stopPing(OperationContext* opCtx, StringData processId) override; private: - Status _unlock(OperationContext* opCtx, const FindAndModifyRequest& request); + Status _unlock(OperationContext* opCtx, const write_ops::FindAndModifyCommand& request); StatusWith<std::vector<BSONObj>> _findOnConfig(OperationContext* opCtx, const ReadPreferenceSetting& readPref, 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 619217a5259..a491d5710c9 100644 --- a/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp +++ b/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp @@ -35,7 +35,6 @@ #include "mongo/bson/json.h" #include "mongo/client/remote_command_targeter_mock.h" #include "mongo/db/commands.h" -#include "mongo/db/query/find_and_modify_request.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/s/shard_server_test_fixture.h" #include "mongo/db/storage/duplicate_key_error_info.h" |