diff options
author | Billy Donahue <billy.donahue@mongodb.com> | 2018-02-27 15:14:14 -0500 |
---|---|---|
committer | Billy Donahue <billy.donahue@mongodb.com> | 2018-03-02 16:31:01 -0500 |
commit | ad94e51e0dd40b0d0c38215a36caf75a4be48415 (patch) | |
tree | 8f621add05d506f88c23e64260780919e1f2d60e /src/mongo/db | |
parent | ae20f392b61ddc90d2191856e76940ca3c7a3ed2 (diff) | |
download | mongo-ad94e51e0dd40b0d0c38215a36caf75a4be48415.tar.gz |
SERVER-33065 CommandReplyBuilder and CommandInvocation
remove publicRun from mr_test.cpp
change explain to take OpMsgRequest
private explain
private allowsAfterClusterTime
private supportsWriteConcern supportsReadConcern
remove publicRun
cluster_explain_cmd.cpp: do not inject "$db" field.
let explain() exceptions escape
update cluster distinct explain
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/commands.cpp | 169 | ||||
-rw-r--r-- | src/mongo/db/commands.h | 243 | ||||
-rw-r--r-- | src/mongo/db/commands/count_cmd.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/distinct.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/explain_cmd.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/commands/find_and_modify.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/find_cmd.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/group_cmd.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/commands/mr_test.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/commands/pipeline_command.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/repl/oplog.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 47 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.h | 7 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_mongod.cpp | 22 |
15 files changed, 378 insertions, 178 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 2aaeda9b5ed..0d8170618d4 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -71,6 +71,23 @@ const WriteConcernOptions kMajorityWriteConcern( WriteConcernOptions::SyncMode::UNSET, Seconds(60)); +// A facade presenting CommandDefinition as an audit::CommandInterface. +class CommandAuditHook : public audit::CommandInterface { +public: + explicit CommandAuditHook(Command* command) : _command(command) {} + + void redactForLogging(mutablebson::Document* cmdObj) const final { + _command->redactForLogging(cmdObj); + } + + std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const final { + return _command->parseNs(dbname, cmdObj); + } + +private: + Command* _command; +}; + } // namespace @@ -80,18 +97,22 @@ const WriteConcernOptions kMajorityWriteConcern( BSONObj CommandHelpers::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) { auto command = globalCommandRegistry()->findCommand(request.getCommandName()); invariant(command); - BSONObjBuilder out; + BufBuilder bb; + CommandReplyBuilder crb(BSONObjBuilder{bb}); try { - bool ok = command->publicRun(opCtx, request, out); - appendCommandStatus(out, ok); + auto invocation = command->parse(opCtx, request); + invocation->run(opCtx, &crb); + auto body = crb.getBodyBuilder(); + CommandHelpers::extractOrAppendOk(body); } catch (const StaleConfigException&) { // These exceptions are intended to be handled at a higher level. throw; } catch (const DBException& ex) { - out.resetToEmpty(); - appendCommandStatus(out, ex.toStatus()); + auto body = crb.getBodyBuilder(); + body.resetToEmpty(); + appendCommandStatus(body, ex.toStatus()); } - return out.obj(); + return BSONObj(bb.release()); } std::string CommandHelpers::parseNsFullyQualified(StringData dbname, const BSONObj& cmdObj) { @@ -167,6 +188,16 @@ void CommandHelpers::appendCommandStatus(BSONObjBuilder& result, } } +bool CommandHelpers::extractOrAppendOk(BSONObjBuilder& reply) { + if (auto okField = reply.asTempObj()["ok"]) { + // If ok is present, use its truthiness. + return okField.trueValue(); + } + // Missing "ok" field is an implied success. + CommandHelpers::appendCommandStatus(reply, true); + return true; +} + void CommandHelpers::appendCommandWCStatus(BSONObjBuilder& result, const Status& awaitReplicationStatus, const WriteConcernResult& wcResult) { @@ -306,10 +337,99 @@ bool CommandHelpers::isHelpRequest(const BSONElement& helpElem) { constexpr StringData CommandHelpers::kHelpFieldName; ////////////////////////////////////////////////////////////// +// CommandReplyBuilder + +CommandReplyBuilder::CommandReplyBuilder(BSONObjBuilder bodyObj) + : _bodyBuf(&bodyObj.bb()), _bodyOffset(bodyObj.offset()) { + // CommandReplyBuilder requires that bodyObj build into an externally-owned buffer. + invariant(!bodyObj.owned()); + bodyObj.doneFast(); +} + +BSONObjBuilder CommandReplyBuilder::getBodyBuilder() const { + return BSONObjBuilder(BSONObjBuilder::ResumeBuildingTag{}, *_bodyBuf, _bodyOffset); +} + +void CommandReplyBuilder::reset() { + getBodyBuilder().resetToEmpty(); +} + +////////////////////////////////////////////////////////////// +// CommandInvocation + +CommandInvocation::~CommandInvocation() = default; + +////////////////////////////////////////////////////////////// // Command +class Command::InvocationShim final : public CommandInvocation { +public: + InvocationShim(OperationContext*, const OpMsgRequest& request, Command* command) + : CommandInvocation(command), + _command(command), + _request(&request), + _dbName(_request->getDatabase().toString()) {} + +private: + void run(OperationContext* opCtx, CommandReplyBuilder* result) override { + try { + BSONObjBuilder bob = result->getBodyBuilder(); + bool ok = _command->enhancedRun(opCtx, *_request, bob); + CommandHelpers::appendCommandStatus(bob, ok); + } catch (const ExceptionFor<ErrorCodes::Unauthorized>&) { + CommandAuditHook hook(_command); + audit::logCommandAuthzCheck( + opCtx->getClient(), *_request, &hook, ErrorCodes::Unauthorized); + throw; + } + } + + void explain(OperationContext* opCtx, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* result) override { + uassertStatusOK(_command->explain(opCtx, *_request, verbosity, result)); + } + + NamespaceString ns() const override { + return NamespaceString(_command->parseNs(_dbName, cmdObj())); + } + + bool supportsWriteConcern() const override { + return _command->supportsWriteConcern(cmdObj()); + } + + Command::AllowedOnSecondary secondaryAllowed(ServiceContext* context) const override { + return _command->secondaryAllowed(context); + } + + bool supportsReadConcern(repl::ReadConcernLevel level) const override { + return _command->supportsReadConcern(_dbName, cmdObj(), level); + } + + bool allowsAfterClusterTime() const override { + return _command->allowsAfterClusterTime(cmdObj()); + } + + void doCheckAuthorization(OperationContext* opCtx) const override { + uassertStatusOK(_command->checkAuthForRequest(opCtx, *_request)); + } + + const BSONObj& cmdObj() const { + return _request->body; + } + + Command* const _command; + const OpMsgRequest* const _request; + const std::string _dbName; +}; + Command::~Command() = default; +std::unique_ptr<CommandInvocation> Command::parse(OperationContext* opCtx, + const OpMsgRequest& request) { + return stdx::make_unique<InvocationShim>(opCtx, request, this); +} + std::string Command::parseNs(const std::string& dbname, const BSONObj& cmdObj) const { BSONElement first = cmdObj.firstElement(); if (first.type() != mongo::String) @@ -335,8 +455,7 @@ Command::Command(StringData name, StringData oldName) } Status Command::explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& request, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const { return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()}; @@ -396,25 +515,6 @@ static Status _checkAuthorizationImpl(Command* c, return Status::OK(); } -namespace { -// A facade presenting CommandDefinition as an audit::CommandInterface. -class CommandAuditHook : public audit::CommandInterface { -public: - explicit CommandAuditHook(Command* command) : _command(command) {} - - void redactForLogging(mutablebson::Document* cmdObj) const final { - _command->redactForLogging(cmdObj); - } - - std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const final { - return _command->parseNs(dbname, cmdObj); - } - -private: - Command* _command; -}; -} // namespace - Status Command::checkAuthorization(Command* c, OperationContext* opCtx, const OpMsgRequest& request) { @@ -427,21 +527,6 @@ Status Command::checkAuthorization(Command* c, return status; } -bool Command::publicRun(OperationContext* opCtx, - const OpMsgRequest& request, - BSONObjBuilder& result) { - try { - return enhancedRun(opCtx, request, result); - } catch (const DBException& e) { - if (e.code() == ErrorCodes::Unauthorized) { - CommandAuditHook hook(this); - audit::logCommandAuthzCheck( - opCtx->getClient(), request, &hook, ErrorCodes::Unauthorized); - } - throw; - } -} - void Command::generateHelpResponse(OperationContext* opCtx, rpc::ReplyBuilderInterface* replyBuilder, const Command& command) { diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h index e9516c75f29..218e2070204 100644 --- a/src/mongo/db/commands.h +++ b/src/mongo/db/commands.h @@ -82,6 +82,13 @@ struct CommandHelpers { static bool appendCommandStatus(BSONObjBuilder& result, const Status& status); /** + * If "ok" field is present in `reply`, uses its truthiness. + * Otherwise, the absence of failure is considered success, `reply` is patched to indicate it. + * Returns true if reply indicates a success. + */ + static bool extractOrAppendOk(BSONObjBuilder& reply); + + /** * Helper for setting a writeConcernError field in the command result object if * a writeConcern error occurs. * @@ -186,6 +193,8 @@ struct CommandHelpers { static constexpr StringData kHelpFieldName = "help"_sd; }; +class CommandInvocation; + /** * Serves as a base for server commands. See the constructor for more details. */ @@ -206,6 +215,9 @@ public: // See https://gcc.gnu.org/wiki/VerboseDiagnostics#missing_vtable virtual ~Command(); + virtual std::unique_ptr<CommandInvocation> parse(OperationContext* opCtx, + const OpMsgRequest& request); + /** * Returns the command's name. This value never changes for the lifetime of the command. */ @@ -239,16 +251,6 @@ public: } /** - * supportsWriteConcern returns true if this command should be parsed for a writeConcern - * field and wait for that write concern to be satisfied after the command runs. - * - * @param cmd is a BSONObj representation of the command that is used to determine if the - * the command supports a write concern. Ex. aggregate only supports write concern - * when $out is provided. - */ - virtual bool supportsWriteConcern(const BSONObj& cmd) const = 0; - - /** * Return true if only the admin ns has privileges to run this command. */ virtual bool adminOnly() const { @@ -266,7 +268,7 @@ public: return false; } - virtual AllowedOnSecondary secondaryAllowed(ServiceContext*) const = 0; + virtual AllowedOnSecondary secondaryAllowed(ServiceContext* context) const = 0; /** * Override and return fales if the command opcounters should not be incremented on @@ -291,23 +293,6 @@ public: } /** - * Commands which can be explained override this method. Any operation which has a query - * part and executes as a tree of execution stages can be explained. A command should - * implement explain by: - * - * 1) Calling its custom parse function in order to parse the command. The output of - * this function should be a CanonicalQuery (representing the query part of the - * operation), and a PlanExecutor which wraps the tree of execution stages. - * - * 2) Calling Explain::explainStages(...) on the PlanExecutor. This is the function - * which knows how to convert an execution stage tree into explain output. - */ - virtual Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - ExplainOptions::Verbosity verbosity, - BSONObjBuilder* out) const; - /** * Checks if the client associated with the given OperationContext is authorized to run this * command. */ @@ -342,33 +327,6 @@ public: } /** - * Returns true if this Command supports the given readConcern level. Takes the command object - * and the name of the database on which it was invoked as arguments, so that readConcern can be - * conditionally rejected based on the command's parameters and/or namespace. - * - * If a readConcern level argument is sent to a command that returns false the command processor - * will reject the command, returning an appropriate error message. - * - * Note that this is never called on mongos. Sharded commands are responsible for forwarding - * the option to the shards as needed. We rely on the shards to fail the commands in the - * cases where it isn't supported. - */ - virtual bool supportsReadConcern(const std::string& dbName, - const BSONObj& cmdObj, - repl::ReadConcernLevel level) const { - return level == repl::ReadConcernLevel::kLocalReadConcern; - } - - /** - * Returns true if command allows afterClusterTime in its readConcern. The command may not allow - * it if it is specifically intended not to take any LockManager locks. Waiting for - * afterClusterTime takes the MODE_IS lock. - */ - virtual bool allowsAfterClusterTime(const BSONObj& cmdObj) const { - return true; - } - - /** * Returns LogicalOp for this command. */ virtual LogicalOp getLogicalOp() const { @@ -401,13 +359,6 @@ public: } /** - * Runs the command. - * - * Forwards to enhancedRun, but additionally runs audit checks if run throws unauthorized. - */ - bool publicRun(OperationContext* opCtx, const OpMsgRequest& request, BSONObjBuilder& result); - - /** * Generates a reply from the 'help' information associated with a command. The state of * the passed ReplyBuilder will be in kOutputDocs after calling this method. */ @@ -455,6 +406,62 @@ public: static bool testCommandsEnabled; private: + class InvocationShim; + + /** + * Commands which can be explained override this method. Any operation which has a query + * part and executes as a tree of execution stages can be explained. A command should + * implement explain by: + * + * 1) Calling its custom parse function in order to parse the command. The output of + * this function should be a CanonicalQuery (representing the query part of the + * operation), and a PlanExecutor which wraps the tree of execution stages. + * + * 2) Calling Explain::explainStages(...) on the PlanExecutor. This is the function + * which knows how to convert an execution stage tree into explain output. + */ + virtual Status explain(OperationContext* opCtx, + const OpMsgRequest& request, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* out) const; + + /** + * supportsWriteConcern returns true if this command should be parsed for a writeConcern + * field and wait for that write concern to be satisfied after the command runs. + * + * @param cmd is a BSONObj representation of the command that is used to determine if the + * the command supports a write concern. Ex. aggregate only supports write concern + * when $out is provided. + */ + virtual bool supportsWriteConcern(const BSONObj& cmd) const = 0; + + /** + * Returns true if this Command supports the given readConcern level. Takes the command object + * and the name of the database on which it was invoked as arguments, so that readConcern can be + * conditionally rejected based on the command's parameters and/or namespace. + * + * If a readConcern level argument is sent to a command that returns false the command processor + * will reject the command, returning an appropriate error message. + * + * Note that this is never called on mongos. Sharded commands are responsible for forwarding + * the option to the shards as needed. We rely on the shards to fail the commands in the + * cases where it isn't supported. + */ + virtual bool supportsReadConcern(const std::string& dbName, + const BSONObj& cmdObj, + repl::ReadConcernLevel level) const { + return level == repl::ReadConcernLevel::kLocalReadConcern; + } + + /** + * Returns true if command allows afterClusterTime in its readConcern. The command may not allow + * it if it is specifically intended not to take any LockManager locks. Waiting for + * afterClusterTime takes the MODE_IS lock. + */ + virtual bool allowsAfterClusterTime(const BSONObj& cmdObj) const { + return true; + } + /** * Runs the command. * @@ -479,6 +486,118 @@ private: ServerStatusMetricField<Counter64> _commandsFailedMetric; }; +class CommandReplyBuilder { +public: + explicit CommandReplyBuilder(BSONObjBuilder bodyObj); + + CommandReplyBuilder(const CommandReplyBuilder&) = delete; + CommandReplyBuilder& operator=(const CommandReplyBuilder&) = delete; + + /** + * Returns a BSONObjBuilder that can be used to build the reply in-place. The returned + * builder (or an object into which it has been moved) must be completed before calling + * any more methods on this object. A builder is completed by a call to `done()` or by + * its destructor. Can be called repeatedly to append multiple things to the reply, as + * long as each returned builder must be completed between calls. + */ + BSONObjBuilder getBodyBuilder() const; + + void reset(); + +private: + BufBuilder* const _bodyBuf; + const std::size_t _bodyOffset; +}; + +/** + * Represents a single invocation of a given command. + */ +class CommandInvocation { +public: + CommandInvocation(const Command* definition) : _definition(definition) {} + virtual ~CommandInvocation(); + + /** + * Runs the command, filling in result. Any exception thrown from here will cause result + * to be reset and filled in with the error. Non-const to permit modifying the request + * type to perform normalization. Calls that return normally without setting an "ok" + * field into result are assumed to have completed successfully. Failure should be + * indicated either by throwing (preferred), or by calling + * `CommandHelpers::extractOrAppendOk`. + */ + virtual void run(OperationContext* opCtx, CommandReplyBuilder* result) = 0; + + virtual void explain(OperationContext* opCtx, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* result) = 0; + + /** + * The primary namespace on which this command operates. May just be the db. + */ + virtual NamespaceString ns() const = 0; + + /** + * Returns true if this command should be parsed for a writeConcern field and wait + * for that write concern to be satisfied after the command runs. + */ + virtual bool supportsWriteConcern() const = 0; + + virtual Command::AllowedOnSecondary secondaryAllowed(ServiceContext* context) const = 0; + + /** + * Returns true if this Command supports the given readConcern level. Takes the command object + * and the name of the database on which it was invoked as arguments, so that readConcern can be + * conditionally rejected based on the command's parameters and/or namespace. + * + * If a readConcern level argument is sent to a command that returns false the command processor + * will reject the command, returning an appropriate error message. + * + * Note that this is never called on mongos. Sharded commands are responsible for forwarding + * the option to the shards as needed. We rely on the shards to fail the commands in the + * cases where it isn't supported. + */ + virtual bool supportsReadConcern(repl::ReadConcernLevel level) const { + return level == repl::ReadConcernLevel::kLocalReadConcern; + } + + /** + * Returns true if command allows afterClusterTime in its readConcern. The command may not allow + * it if it is specifically intended not to take any LockManager locks. Waiting for + * afterClusterTime takes the MODE_IS lock. + */ + virtual bool allowsAfterClusterTime() const { + return true; + } + + /** + * The command definition that this invocation runs. + * Note: nonvirtual. + */ + const Command* definition() const { + return _definition; + } + + /** + * Throws DBException, most likely `ErrorCodes::Unauthorized`, unless + * the client executing "opCtx" is authorized to run the given command + * with the given parameters on the given named database. + * Note: nonvirtual. + */ + void checkAuthorization(OperationContext* opCtx) const; + +protected: + ResourcePattern resourcePattern() const; + +private: + /** + * Polymorphic extension point for `checkAuthorization`. + * Throws unless `opCtx`'s client is authorized to `run()` this. + */ + virtual void doCheckAuthorization(OperationContext* opCtx) const = 0; + + const Command* const _definition; +}; + /** * A subclass of Command that only cares about the BSONObj body and doesn't need access to document * sequences. diff --git a/src/mongo/db/commands/count_cmd.cpp b/src/mongo/db/commands/count_cmd.cpp index d2e35f86fd6..a91561f6da6 100644 --- a/src/mongo/db/commands/count_cmd.cpp +++ b/src/mongo/db/commands/count_cmd.cpp @@ -110,10 +110,11 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& opMsgRequest, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const override { + std::string dbname = opMsgRequest.getDatabase().toString(); + const BSONObj& cmdObj = opMsgRequest.body; // Acquire locks and resolve possible UUID. The RAII object is optional, because in the case // of a view, the locks need to be released. boost::optional<AutoGetCollectionForReadCommand> ctx; diff --git a/src/mongo/db/commands/distinct.cpp b/src/mongo/db/commands/distinct.cpp index eba4fdd51bc..ae99b5a2332 100644 --- a/src/mongo/db/commands/distinct.cpp +++ b/src/mongo/db/commands/distinct.cpp @@ -110,10 +110,11 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& request, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const override { + std::string dbname = request.getDatabase().toString(); + const BSONObj& cmdObj = request.body; // Acquire locks and resolve possible UUID. The RAII object is optional, because in the case // of a view, the locks need to be released. boost::optional<AutoGetCollectionForReadCommand> ctx; diff --git a/src/mongo/db/commands/explain_cmd.cpp b/src/mongo/db/commands/explain_cmd.cpp index 8f0ce39a66a..7cdd1441d3f 100644 --- a/src/mongo/db/commands/explain_cmd.cpp +++ b/src/mongo/db/commands/explain_cmd.cpp @@ -154,13 +154,9 @@ public: } // Actually call the nested command's explain(...) method. - Status explainStatus = - commToExplain->explain(opCtx, dbname, explainObj, verbosity.getValue(), &result); - if (!explainStatus.isOK()) { - return CommandHelpers::appendCommandStatus(result, explainStatus); - } - - return true; + commToExplain->parse(opCtx, OpMsgRequest::fromDBAndBody(dbname, explainObj)) + ->explain(opCtx, verbosity.getValue(), &result); + return CommandHelpers::extractOrAppendOk(result); } } cmdExplain; diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index e836e7aa737..9757e8e450d 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -246,10 +246,11 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbName, - const BSONObj& cmdObj, + const OpMsgRequest& request, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) 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(); diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index 451171fc348..7cd41a2a6a8 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -126,10 +126,11 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& request, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const override { + std::string dbname = request.getDatabase().toString(); + const BSONObj& cmdObj = request.body; // Acquire locks and resolve possible UUID. The RAII object is optional, because in the case // of a view, the locks need to be released. boost::optional<AutoGetCollectionForReadCommand> ctx; diff --git a/src/mongo/db/commands/group_cmd.cpp b/src/mongo/db/commands/group_cmd.cpp index 547dee36087..c4c67983ed8 100644 --- a/src/mongo/db/commands/group_cmd.cpp +++ b/src/mongo/db/commands/group_cmd.cpp @@ -117,11 +117,12 @@ private: return nss.ns(); } - virtual Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - ExplainOptions::Verbosity verbosity, - BSONObjBuilder* out) const { + Status explain(OperationContext* opCtx, + const OpMsgRequest& request, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* out) const override { + std::string dbname = request.getDatabase().toString(); + const BSONObj& cmdObj = request.body; GroupRequest groupRequest; Status parseRequestStatus = _parseRequest(dbname, cmdObj, &groupRequest); if (!parseRequestStatus.isOK()) { diff --git a/src/mongo/db/commands/mr_test.cpp b/src/mongo/db/commands/mr_test.cpp index 068699d88b3..8541e5f3f74 100644 --- a/src/mongo/db/commands/mr_test.cpp +++ b/src/mongo/db/commands/mr_test.cpp @@ -423,11 +423,11 @@ Status MapReduceCommandTest::_runCommand(StringData mapCode, StringData reduceCo ASSERT(command) << "Unable to look up mapReduce command"; auto request = OpMsgRequest::fromDBAndBody(inputNss.db(), _makeCmdObj(mapCode, reduceCode)); - BSONObjBuilder result; - auto success = command->publicRun(_opCtx.get(), request, result); - if (!success) { - auto status = getStatusFromCommandResult(result.obj()); - ASSERT_NOT_OK(status); + BufBuilder bb; + CommandReplyBuilder crb(BSONObjBuilder{bb}); + command->parse(_opCtx.get(), request)->run(_opCtx.get(), &crb); + auto status = getStatusFromCommandResult(crb.getBodyBuilder().asTempObj()); + if (!status.isOK()) { return status.withContext(str::stream() << "mapReduce command failed: " << request.body); } diff --git a/src/mongo/db/commands/pipeline_command.cpp b/src/mongo/db/commands/pipeline_command.cpp index 32ede313ee5..c37d19bd12c 100644 --- a/src/mongo/db/commands/pipeline_command.cpp +++ b/src/mongo/db/commands/pipeline_command.cpp @@ -96,10 +96,11 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& request, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const override { + std::string dbname = request.getDatabase().toString(); + const BSONObj& cmdObj = request.body; const auto aggregationRequest = uassertStatusOK(AggregationRequest::parseFromBSON(dbname, cmdObj, verbosity)); diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp index 54410f26c0e..e1730107747 100644 --- a/src/mongo/db/commands/write_commands/write_commands.cpp +++ b/src/mongo/db/commands/write_commands/write_commands.cpp @@ -303,11 +303,9 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& opMsgRequest, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const final { - const auto opMsgRequest(OpMsgRequest::fromDBAndBody(dbname, cmdObj)); const auto batch = UpdateOp::parse(opMsgRequest); uassert(ErrorCodes::InvalidLength, "explained write batches must be of size 1", @@ -370,11 +368,9 @@ public: } Status explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, + const OpMsgRequest& opMsgRequest, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const final { - const auto opMsgRequest(OpMsgRequest::fromDBAndBody(dbname, cmdObj)); const auto batch = DeleteOp::parse(opMsgRequest); uassert(ErrorCodes::InvalidLength, "explained write batches must be of size 1", diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 6eab8fc2f4a..47866b957ea 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -391,11 +391,11 @@ void _logOpsInner(OperationContext* opCtx, checkOplogInsert(oplogCollection->insertDocumentsForOplog(opCtx, writers, timestamps, nDocs)); // Set replCoord last optime only after we're sure the WUOW didn't abort and roll back. - opCtx->recoveryUnit()->onCommit([opCtx, replCoord, finalOpTime] { + opCtx->recoveryUnit()->onCommit([replCoord, finalOpTime] { // Optimes on the primary should always represent consistent database states. replCoord->setMyLastAppliedOpTimeForward( finalOpTime, ReplicationCoordinator::DataConsistency::Consistent); - ReplClientInfo::forClient(opCtx->getClient()).setLastOp(finalOpTime); + ReplClientInfo::forClient(Client::getCurrent()).setLastOp(finalOpTime); }); } diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 4dac385d6b0..753add5bb6d 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -319,8 +319,7 @@ void appendReplyMetadata(OperationContext* opCtx, * Given the specified command, returns an effective read concern which should be used or an error * if the read concern is not valid for the command. */ -StatusWith<repl::ReadConcernArgs> _extractReadConcern(const Command* command, - const std::string& dbName, +StatusWith<repl::ReadConcernArgs> _extractReadConcern(const CommandInvocation* invocation, const BSONObj& cmdObj) { repl::ReadConcernArgs readConcernArgs; @@ -329,7 +328,7 @@ StatusWith<repl::ReadConcernArgs> _extractReadConcern(const Command* command, return readConcernParseStatus; } - if (!command->supportsReadConcern(dbName, cmdObj, readConcernArgs.getLevel())) { + if (!invocation->supportsReadConcern(readConcernArgs.getLevel())) { return {ErrorCodes::InvalidOptions, str::stream() << "Command does not support read concern " << readConcernArgs.toString()}; @@ -388,11 +387,12 @@ LogicalTime computeOperationTime(OperationContext* opCtx, } bool runCommandImpl(OperationContext* opCtx, - Command* command, + CommandInvocation* invocation, const OpMsgRequest& request, rpc::ReplyBuilderInterface* replyBuilder, LogicalTime startOperationTime, const ServiceEntryPointCommon::Hooks& behaviors) { + const Command* command = invocation->definition(); auto bytesToReserve = command->reserveBytesForReply(); // SERVER-22100: In Windows DEBUG builds, the CRT heap debugging overhead, in conjunction with the @@ -409,14 +409,13 @@ bool runCommandImpl(OperationContext* opCtx, // run expects const db std::string (can't bind to temporary) const std::string db = request.getDatabase().toString(); - BSONObjBuilder inPlaceReplyBob = replyBuilder->getInPlaceReplyBuilder(bytesToReserve); + CommandReplyBuilder crb(replyBuilder->getInPlaceReplyBuilder(bytesToReserve)); - behaviors.waitForReadConcern(opCtx, command, db, request, cmd); + behaviors.waitForReadConcern(opCtx, invocation, db, request, cmd); - bool result; - if (!command->supportsWriteConcern(cmd)) { + if (!invocation->supportsWriteConcern()) { behaviors.uassertCommandDoesNotSpecifyWriteConcern(cmd); - result = command->publicRun(opCtx, request, inPlaceReplyBob); + invocation->run(opCtx, &crb); } else { auto wcResult = uassertStatusOK(extractWriteConcern(opCtx, cmd, db)); @@ -428,10 +427,9 @@ bool runCommandImpl(OperationContext* opCtx, opCtx->setWriteConcern(wcResult); ON_BLOCK_EXIT([&] { behaviors.waitForWriteConcern( - opCtx, command->getName(), lastOpBeforeRun, &inPlaceReplyBob); + opCtx, invocation->definition()->getName(), lastOpBeforeRun, crb.getBodyBuilder()); }); - - result = command->publicRun(opCtx, request, inPlaceReplyBob); + invocation->run(opCtx, &crb); // Nothing in run() should change the writeConcern. dassert(SimpleBSONObjComparator::kInstance.evaluate(opCtx->getWriteConcern().toBSON() == @@ -440,9 +438,11 @@ bool runCommandImpl(OperationContext* opCtx, behaviors.waitForLinearizableReadConcern(opCtx); - CommandHelpers::appendCommandStatus(inPlaceReplyBob, result); - - behaviors.attachCurOpErrInfo(opCtx, inPlaceReplyBob); + const bool ok = [&] { + auto body = crb.getBodyBuilder(); + return CommandHelpers::extractOrAppendOk(body); + }(); + behaviors.attachCurOpErrInfo(opCtx, crb.getBodyBuilder().asTempObj()); auto operationTime = computeOperationTime( opCtx, startOperationTime, repl::ReadConcernArgs::get(opCtx).getLevel()); @@ -450,16 +450,15 @@ bool runCommandImpl(OperationContext* opCtx, // An uninitialized operation time means the cluster time is not propagated, so the operation // time should not be attached to the response. if (operationTime != LogicalTime::kUninitialized) { - operationTime.appendAsOperationTime(&inPlaceReplyBob); + auto body = crb.getBodyBuilder(); + operationTime.appendAsOperationTime(&body); } - inPlaceReplyBob.doneFast(); - BSONObjBuilder metadataBob; appendReplyMetadata(opCtx, request, &metadataBob); replyBuilder->setMetadata(metadataBob.done()); - return result; + return ok; } /** @@ -474,8 +473,8 @@ void execCommandDatabase(OperationContext* opCtx, const OpMsgRequest& request, rpc::ReplyBuilderInterface* replyBuilder, const ServiceEntryPointCommon::Hooks& behaviors) { - auto startOperationTime = getClientOperationTime(opCtx); + auto invocation = command->parse(opCtx, request); try { { stdx::lock_guard<Client> lk(*opCtx->getClient()); @@ -628,7 +627,7 @@ void execCommandDatabase(OperationContext* opCtx, } auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); - readConcernArgs = uassertStatusOK(_extractReadConcern(command, dbname, request.body)); + readConcernArgs = uassertStatusOK(_extractReadConcern(invocation.get(), request.body)); // TODO SERVER-33354: Remove whitelist. if (readConcernArgs.getLevel() == repl::ReadConcernLevel::kSnapshotReadConcern) { @@ -695,8 +694,8 @@ void execCommandDatabase(OperationContext* opCtx, } sessionTxnState.unstashTransactionResources(); - retval = - runCommandImpl(opCtx, command, request, replyBuilder, startOperationTime, behaviors); + retval = runCommandImpl( + opCtx, invocation.get(), request, replyBuilder, startOperationTime, behaviors); if (retval) { if (opCtx->getWriteUnitOfWork()) { @@ -729,7 +728,7 @@ void execCommandDatabase(OperationContext* opCtx, // Note: the read concern may not have been successfully or yet placed on the opCtx, so // parsing it separately here. const std::string db = request.getDatabase().toString(); - auto readConcernArgsStatus = _extractReadConcern(command, db, request.body); + auto readConcernArgsStatus = _extractReadConcern(invocation.get(), request.body); auto operationTime = readConcernArgsStatus.isOK() ? computeOperationTime( opCtx, startOperationTime, readConcernArgsStatus.getValue().getLevel()) diff --git a/src/mongo/db/service_entry_point_common.h b/src/mongo/db/service_entry_point_common.h index 0ec781f8c0f..a7e7155881a 100644 --- a/src/mongo/db/service_entry_point_common.h +++ b/src/mongo/db/service_entry_point_common.h @@ -61,20 +61,19 @@ struct ServiceEntryPointCommon { virtual ~Hooks(); virtual bool lockedForWriting() const = 0; virtual void waitForReadConcern(OperationContext* opCtx, - const Command* command, + const CommandInvocation* invocation, const std::string& db, const OpMsgRequest& request, const BSONObj& cmdObj) const = 0; virtual void waitForWriteConcern(OperationContext* opCtx, const std::string& commandName, const repl::OpTime& lastOpBeforeRun, - BSONObjBuilder* commandResponseBuilder) const = 0; + BSONObjBuilder commandResponseBuilder) const = 0; virtual void waitForLinearizableReadConcern(OperationContext* opCtx) const = 0; virtual void uassertCommandDoesNotSpecifyWriteConcern(const BSONObj& cmdObj) const = 0; - virtual void attachCurOpErrInfo(OperationContext* opCtx, - BSONObjBuilder& replyObj) const = 0; + virtual void attachCurOpErrInfo(OperationContext* opCtx, const BSONObj& replyObj) const = 0; }; static DbResponse handleRequest(OperationContext* opCtx, const Message& m, const Hooks& hooks); diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp index 3b403f52ecb..a442f40ee27 100644 --- a/src/mongo/db/service_entry_point_mongod.cpp +++ b/src/mongo/db/service_entry_point_mongod.cpp @@ -51,12 +51,12 @@ public: } void waitForReadConcern(OperationContext* opCtx, - const Command* command, + const CommandInvocation* invocation, const std::string& db, const OpMsgRequest& request, const BSONObj& cmdObj) const override { Status rcStatus = mongo::waitForReadConcern( - opCtx, repl::ReadConcernArgs::get(opCtx), command->allowsAfterClusterTime(cmdObj)); + opCtx, repl::ReadConcernArgs::get(opCtx), invocation->allowsAfterClusterTime()); if (!rcStatus.isOK()) { if (rcStatus == ErrorCodes::ExceededTimeLimit) { const int debugLevel = @@ -64,7 +64,7 @@ public: LOG(debugLevel) << "Command on database " << db << " timed out waiting for read concern to be satisfied. Command: " << redact(ServiceEntryPointCommon::getRedactedCopyForLogging( - command, request.body)); + invocation->definition(), request.body)); } uassertStatusOK(rcStatus); @@ -74,7 +74,7 @@ public: void waitForWriteConcern(OperationContext* opCtx, const std::string& commandName, const repl::OpTime& lastOpBeforeRun, - BSONObjBuilder* commandResponseBuilder) const override { + BSONObjBuilder commandResponseBuilder) const override { auto lastOpAfterRun = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp(); // Ensures that if we tried to do a write, we wait for write concern, even if that write was // a noop. @@ -88,15 +88,15 @@ public: auto waitForWCStatus = mongo::waitForWriteConcern(opCtx, lastOpAfterRun, opCtx->getWriteConcern(), &res); - CommandHelpers::appendCommandWCStatus(*commandResponseBuilder, waitForWCStatus, res); + CommandHelpers::appendCommandWCStatus(commandResponseBuilder, waitForWCStatus, res); // SERVER-22421: This code is to ensure error response backwards compatibility with the // user management commands. This can be removed in 3.6. if (!waitForWCStatus.isOK() && CommandHelpers::isUserManagementCommand(commandName)) { - BSONObj temp = commandResponseBuilder->asTempObj().copy(); - commandResponseBuilder->resetToEmpty(); - CommandHelpers::appendCommandStatus(*commandResponseBuilder, waitForWCStatus); - commandResponseBuilder->appendElementsUnique(temp); + BSONObj temp = commandResponseBuilder.asTempObj().copy(); + commandResponseBuilder.resetToEmpty(); + CommandHelpers::appendCommandStatus(commandResponseBuilder, waitForWCStatus); + commandResponseBuilder.appendElementsUnique(temp); } } @@ -115,8 +115,8 @@ public: } } - void attachCurOpErrInfo(OperationContext* opCtx, BSONObjBuilder& replyObj) const override { - CurOp::get(opCtx)->debug().errInfo = getStatusFromCommandResult(replyObj.asTempObj()); + void attachCurOpErrInfo(OperationContext* opCtx, const BSONObj& replyObj) const override { + CurOp::get(opCtx)->debug().errInfo = getStatusFromCommandResult(replyObj); } }; |