summaryrefslogtreecommitdiff
path: root/src/mongo/db/commands.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/commands.cpp')
-rw-r--r--src/mongo/db/commands.cpp381
1 files changed, 194 insertions, 187 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp
index 25ee7429189..b7b67b5092d 100644
--- a/src/mongo/db/commands.cpp
+++ b/src/mongo/db/commands.cpp
@@ -73,58 +73,30 @@ const WriteConcernOptions kMajorityWriteConcern(
} // namespace
-Command::~Command() = default;
-
-BSONObj Command::appendPassthroughFields(const BSONObj& cmdObjWithPassthroughFields,
- const BSONObj& request) {
- BSONObjBuilder b;
- b.appendElements(request);
- for (const auto& elem :
- Command::filterCommandRequestForPassthrough(cmdObjWithPassthroughFields)) {
- const auto name = elem.fieldNameStringData();
- if (Command::isGenericArgument(name) && !request.hasField(name)) {
- b.append(elem);
- }
- }
- return b.obj();
-}
-
-BSONObj Command::appendMajorityWriteConcern(const BSONObj& cmdObj) {
-
- WriteConcernOptions newWC = kMajorityWriteConcern;
-
- if (cmdObj.hasField(kWriteConcernField)) {
- auto wc = cmdObj.getField(kWriteConcernField);
- // The command has a writeConcern field and it's majority, so we can
- // return it as-is.
- if (wc["w"].ok() && wc["w"].str() == "majority") {
- return cmdObj;
- }
- if (wc["wtimeout"].ok()) {
- // They set a timeout, but aren't using majority WC. We want to use their
- // timeout along with majority WC.
- newWC = WriteConcernOptions(WriteConcernOptions::kMajority,
- WriteConcernOptions::SyncMode::UNSET,
- wc["wtimeout"].Number());
- }
- }
+//////////////////////////////////////////////////////////////
+// CommandHelpers
- // Append all original fields except the writeConcern field to the new command.
- BSONObjBuilder cmdObjWithWriteConcern;
- for (const auto& elem : cmdObj) {
- const auto name = elem.fieldNameStringData();
- if (name != "writeConcern" && !cmdObjWithWriteConcern.hasField(name)) {
- cmdObjWithWriteConcern.append(elem);
- }
+BSONObj CommandHelpers::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) {
+ auto command = globalCommandRegistry()->findCommand(request.getCommandName());
+ invariant(command);
+ BSONObjBuilder out;
+ try {
+ bool ok = command->publicRun(opCtx, request, out);
+ appendCommandStatus(out, ok);
+ } catch (const StaleConfigException&) {
+ // These exceptions are intended to be handled at a higher level and cannot losslessly
+ // round-trip through Status.
+ throw;
+ } catch (const DBException& ex) {
+ out.resetToEmpty();
+ appendCommandStatus(out, ex.toStatus());
}
-
- // Finally, add the new write concern.
- cmdObjWithWriteConcern.append(kWriteConcernField, newWC.toBSON());
- return cmdObjWithWriteConcern.obj();
+ return out.obj();
}
-std::string Command::parseNsFullyQualified(const std::string& dbname, const BSONObj& cmdObj) {
+std::string CommandHelpers::parseNsFullyQualified(const std::string& dbname,
+ const BSONObj& cmdObj) {
BSONElement first = cmdObj.firstElement();
uassert(ErrorCodes::BadValue,
str::stream() << "collection name has invalid type " << typeName(first.type()),
@@ -136,8 +108,8 @@ std::string Command::parseNsFullyQualified(const std::string& dbname, const BSON
return nss.ns();
}
-NamespaceString Command::parseNsCollectionRequired(const std::string& dbname,
- const BSONObj& cmdObj) {
+NamespaceString CommandHelpers::parseNsCollectionRequired(const std::string& dbname,
+ const BSONObj& cmdObj) {
// Accepts both BSON String and Symbol for collection name per SERVER-16260
// TODO(kangas) remove Symbol support in MongoDB 3.0 after Ruby driver audit
BSONElement first = cmdObj.firstElement();
@@ -151,9 +123,9 @@ NamespaceString Command::parseNsCollectionRequired(const std::string& dbname,
return nss;
}
-NamespaceString Command::parseNsOrUUID(OperationContext* opCtx,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+NamespaceString CommandHelpers::parseNsOrUUID(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
BSONElement first = cmdObj.firstElement();
if (first.type() == BinData && first.binDataType() == BinDataType::newUUID) {
UUIDCatalog& catalog = UUIDCatalog::get(opCtx);
@@ -178,51 +150,11 @@ NamespaceString Command::parseNsOrUUID(OperationContext* opCtx,
}
}
-std::string Command::parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- BSONElement first = cmdObj.firstElement();
- if (first.type() != mongo::String)
- return dbname;
-
- return str::stream() << dbname << '.' << cmdObj.firstElement().valueStringData();
-}
-
-ResourcePattern Command::parseResourcePattern(const std::string& dbname,
- const BSONObj& cmdObj) const {
- const std::string ns = parseNs(dbname, cmdObj);
- if (!NamespaceString::validCollectionComponent(ns)) {
- return ResourcePattern::forDatabaseName(ns);
- }
- return ResourcePattern::forExactNamespace(NamespaceString(ns));
-}
-
-Command::Command(StringData name, StringData oldName)
- : _name(name.toString()),
- _commandsExecutedMetric("commands." + _name + ".total", &_commandsExecuted),
- _commandsFailedMetric("commands." + _name + ".failed", &_commandsFailed) {
- globalCommandRegistry()->registerCommand(this, name, oldName);
-}
-
-void Command::help(std::stringstream& help) const {
- help << "no help defined";
-}
-
-Status Command::explain(OperationContext* opCtx,
- const std::string& dbname,
- const BSONObj& cmdObj,
- ExplainOptions::Verbosity verbosity,
- BSONObjBuilder* out) const {
- return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()};
-}
-
-BSONObj Command::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) {
- return CommandHelpers::runCommandDirectly(opCtx, request);
-}
-
-Command* Command::findCommand(StringData name) {
+Command* CommandHelpers::findCommand(StringData name) {
return globalCommandRegistry()->findCommand(name);
}
-bool Command::appendCommandStatus(BSONObjBuilder& result, const Status& status) {
+bool CommandHelpers::appendCommandStatus(BSONObjBuilder& result, const Status& status) {
appendCommandStatus(result, status.isOK(), status.reason());
BSONObj tmp = result.asTempObj();
if (!status.isOK() && !tmp.hasField("code")) {
@@ -235,7 +167,9 @@ bool Command::appendCommandStatus(BSONObjBuilder& result, const Status& status)
return status.isOK();
}
-void Command::appendCommandStatus(BSONObjBuilder& result, bool ok, const std::string& errmsg) {
+void CommandHelpers::appendCommandStatus(BSONObjBuilder& result,
+ bool ok,
+ const std::string& errmsg) {
BSONObj tmp = result.asTempObj();
bool have_ok = tmp.hasField("ok");
bool need_errmsg = !ok && !tmp.hasField("errmsg");
@@ -248,9 +182,9 @@ void Command::appendCommandStatus(BSONObjBuilder& result, bool ok, const std::st
}
}
-void Command::appendCommandWCStatus(BSONObjBuilder& result,
- const Status& awaitReplicationStatus,
- const WriteConcernResult& wcResult) {
+void CommandHelpers::appendCommandWCStatus(BSONObjBuilder& result,
+ const Status& awaitReplicationStatus,
+ const WriteConcernResult& wcResult) {
if (!awaitReplicationStatus.isOK() && !result.hasField("writeConcernError")) {
WriteConcernErrorDetail wcError;
wcError.setErrCode(awaitReplicationStatus.code());
@@ -262,6 +196,165 @@ void Command::appendCommandWCStatus(BSONObjBuilder& result,
}
}
+BSONObj CommandHelpers::appendPassthroughFields(const BSONObj& cmdObjWithPassthroughFields,
+ const BSONObj& request) {
+ BSONObjBuilder b;
+ b.appendElements(request);
+ for (const auto& elem : filterCommandRequestForPassthrough(cmdObjWithPassthroughFields)) {
+ const auto name = elem.fieldNameStringData();
+ if (isGenericArgument(name) && !request.hasField(name)) {
+ b.append(elem);
+ }
+ }
+ return b.obj();
+}
+
+BSONObj CommandHelpers::appendMajorityWriteConcern(const BSONObj& cmdObj) {
+ WriteConcernOptions newWC = kMajorityWriteConcern;
+
+ if (cmdObj.hasField(kWriteConcernField)) {
+ auto wc = cmdObj.getField(kWriteConcernField);
+ // The command has a writeConcern field and it's majority, so we can
+ // return it as-is.
+ if (wc["w"].ok() && wc["w"].str() == "majority") {
+ return cmdObj;
+ }
+
+ if (wc["wtimeout"].ok()) {
+ // They set a timeout, but aren't using majority WC. We want to use their
+ // timeout along with majority WC.
+ newWC = WriteConcernOptions(WriteConcernOptions::kMajority,
+ WriteConcernOptions::SyncMode::UNSET,
+ wc["wtimeout"].Number());
+ }
+ }
+
+ // Append all original fields except the writeConcern field to the new command.
+ BSONObjBuilder cmdObjWithWriteConcern;
+ for (const auto& elem : cmdObj) {
+ const auto name = elem.fieldNameStringData();
+ if (name != "writeConcern" && !cmdObjWithWriteConcern.hasField(name)) {
+ cmdObjWithWriteConcern.append(elem);
+ }
+ }
+
+ // Finally, add the new write concern.
+ cmdObjWithWriteConcern.append(kWriteConcernField, newWC.toBSON());
+ return cmdObjWithWriteConcern.obj();
+}
+
+namespace {
+const stdx::unordered_set<std::string> userManagementCommands{"createUser",
+ "updateUser",
+ "dropUser",
+ "dropAllUsersFromDatabase",
+ "grantRolesToUser",
+ "revokeRolesFromUser",
+ "createRole",
+ "updateRole",
+ "dropRole",
+ "dropAllRolesFromDatabase",
+ "grantPrivilegesToRole",
+ "revokePrivilegesFromRole",
+ "grantRolesToRole",
+ "revokeRolesFromRole",
+ "_mergeAuthzCollections"};
+} // namespace
+
+bool CommandHelpers::isUserManagementCommand(const std::string& name) {
+ return userManagementCommands.count(name);
+}
+
+BSONObj CommandHelpers::filterCommandRequestForPassthrough(const BSONObj& cmdObj) {
+ BSONObjBuilder bob;
+ for (auto elem : cmdObj) {
+ const auto name = elem.fieldNameStringData();
+ if (name == "$readPreference") {
+ BSONObjBuilder(bob.subobjStart("$queryOptions")).append(elem);
+ } else if (!isGenericArgument(name) || //
+ name == "$queryOptions" || //
+ name == "maxTimeMS" || //
+ name == "readConcern" || //
+ name == "writeConcern" || //
+ name == "lsid" || //
+ name == "txnNumber") {
+ // This is the whitelist of generic arguments that commands can be trusted to blindly
+ // forward to the shards.
+ bob.append(elem);
+ }
+ }
+ return bob.obj();
+}
+
+void CommandHelpers::filterCommandReplyForPassthrough(const BSONObj& cmdObj,
+ BSONObjBuilder* output) {
+ for (auto elem : cmdObj) {
+ const auto name = elem.fieldNameStringData();
+ if (name == "$configServerState" || //
+ name == "$gleStats" || //
+ name == "$clusterTime" || //
+ name == "$oplogQueryData" || //
+ name == "$replData" || //
+ name == "operationTime") {
+ continue;
+ }
+ output->append(elem);
+ }
+}
+
+BSONObj CommandHelpers::filterCommandReplyForPassthrough(const BSONObj& cmdObj) {
+ BSONObjBuilder bob;
+ filterCommandReplyForPassthrough(cmdObj, &bob);
+ return bob.obj();
+}
+
+bool CommandHelpers::isHelpRequest(const BSONElement& helpElem) {
+ return !helpElem.eoo() && helpElem.trueValue();
+}
+
+constexpr StringData CommandHelpers::kHelpFieldName;
+
+//////////////////////////////////////////////////////////////
+// Command
+
+Command::~Command() = default;
+
+std::string Command::parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ BSONElement first = cmdObj.firstElement();
+ if (first.type() != mongo::String)
+ return dbname;
+
+ return str::stream() << dbname << '.' << cmdObj.firstElement().valueStringData();
+}
+
+ResourcePattern Command::parseResourcePattern(const std::string& dbname,
+ const BSONObj& cmdObj) const {
+ const std::string ns = parseNs(dbname, cmdObj);
+ if (!NamespaceString::validCollectionComponent(ns)) {
+ return ResourcePattern::forDatabaseName(ns);
+ }
+ return ResourcePattern::forExactNamespace(NamespaceString(ns));
+}
+
+Command::Command(StringData name, StringData oldName)
+ : _name(name.toString()),
+ _commandsExecutedMetric("commands." + _name + ".total", &_commandsExecuted),
+ _commandsFailedMetric("commands." + _name + ".failed", &_commandsFailed) {
+ globalCommandRegistry()->registerCommand(this, name, oldName);
+}
+
+void Command::help(std::stringstream& help) const {
+ help << "no help defined";
+}
+
+Status Command::explain(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ ExplainOptions::Verbosity verbosity,
+ BSONObjBuilder* out) const {
+ return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()};
+}
+
Status BasicCommand::checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) {
uassertNoDocumentSequences(request);
return checkAuthForOperation(opCtx, request.getDatabase().toString(), request.body);
@@ -372,12 +465,6 @@ bool Command::publicRun(OperationContext* opCtx,
}
}
-bool Command::isHelpRequest(const BSONElement& helpElem) {
- return !helpElem.eoo() && helpElem.trueValue();
-}
-
-const char Command::kHelpFieldName[] = "help";
-
void Command::generateHelpResponse(OperationContext* opCtx,
rpc::ReplyBuilderInterface* replyBuilder,
const Command& command) {
@@ -391,28 +478,6 @@ void Command::generateHelpResponse(OperationContext* opCtx,
replyBuilder->setMetadata(rpc::makeEmptyMetadata());
}
-namespace {
-const stdx::unordered_set<std::string> userManagementCommands{"createUser",
- "updateUser",
- "dropUser",
- "dropAllUsersFromDatabase",
- "grantRolesToUser",
- "revokeRolesFromUser",
- "createRole",
- "updateRole",
- "dropRole",
- "dropAllRolesFromDatabase",
- "grantPrivilegesToRole",
- "revokePrivilegesFromRole",
- "grantRolesToRole",
- "revokeRolesFromRole",
- "_mergeAuthzCollections"};
-} // namespace
-
-bool Command::isUserManagementCommand(const std::string& name) {
- return userManagementCommands.count(name);
-}
-
void BasicCommand::uassertNoDocumentSequences(const OpMsgRequest& request) {
uassert(40472,
str::stream() << "The " << getName() << " command does not support document sequences.",
@@ -433,52 +498,13 @@ bool ErrmsgCommandDeprecated::run(OperationContext* opCtx,
std::string errmsg;
auto ok = errmsgRun(opCtx, db, cmdObj, errmsg, result);
if (!errmsg.empty()) {
- appendCommandStatus(result, ok, errmsg);
+ CommandHelpers::appendCommandStatus(result, ok, errmsg);
}
return ok;
}
-BSONObj Command::filterCommandRequestForPassthrough(const BSONObj& cmdObj) {
- BSONObjBuilder bob;
- for (auto elem : cmdObj) {
- const auto name = elem.fieldNameStringData();
- if (name == "$readPreference") {
- BSONObjBuilder(bob.subobjStart("$queryOptions")).append(elem);
- } else if (!Command::isGenericArgument(name) || //
- name == "$queryOptions" || //
- name == "maxTimeMS" || //
- name == "readConcern" || //
- name == "writeConcern" || //
- name == "lsid" || //
- name == "txnNumber") {
- // This is the whitelist of generic arguments that commands can be trusted to blindly
- // forward to the shards.
- bob.append(elem);
- }
- }
- return bob.obj();
-}
-
-void Command::filterCommandReplyForPassthrough(const BSONObj& cmdObj, BSONObjBuilder* output) {
- for (auto elem : cmdObj) {
- const auto name = elem.fieldNameStringData();
- if (name == "$configServerState" || //
- name == "$gleStats" || //
- name == "$clusterTime" || //
- name == "$oplogQueryData" || //
- name == "$replData" || //
- name == "operationTime") {
- continue;
- }
- output->append(elem);
- }
-}
-
-BSONObj Command::filterCommandReplyForPassthrough(const BSONObj& cmdObj) {
- BSONObjBuilder bob;
- filterCommandReplyForPassthrough(cmdObj, &bob);
- return bob.obj();
-}
+//////////////////////////////////////////////////////////////
+// CommandRegistry
void CommandRegistry::registerCommand(Command* command, StringData name, StringData oldName) {
for (StringData key : {name, oldName}) {
@@ -499,25 +525,6 @@ Command* CommandRegistry::findCommand(StringData name) const {
return it->second;
}
-BSONObj CommandHelpers::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) {
- auto command = globalCommandRegistry()->findCommand(request.getCommandName());
- invariant(command);
-
- BSONObjBuilder out;
- try {
- bool ok = command->publicRun(opCtx, request, out);
- Command::appendCommandStatus(out, ok);
- } catch (const StaleConfigException&) {
- // These exceptions are intended to be handled at a higher level and cannot losslessly
- // round-trip through Status.
- throw;
- } catch (const DBException& ex) {
- out.resetToEmpty();
- Command::appendCommandStatus(out, ex.toStatus());
- }
- return out.obj();
-}
-
CommandRegistry* globalCommandRegistry() {
static auto reg = new CommandRegistry();
return reg;