summaryrefslogtreecommitdiff
path: root/src/mongo/s/commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/s/commands')
-rw-r--r--src/mongo/s/commands/SConscript2
-rw-r--r--src/mongo/s/commands/cluster_db_stats_cmd.cpp5
-rw-r--r--src/mongo/s/commands/cluster_explain.cpp37
-rw-r--r--src/mongo/s/commands/cluster_explain.h3
-rw-r--r--src/mongo/s/commands/cluster_explain_test.cpp103
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.h34
-rw-r--r--src/mongo/s/commands/cluster_fle2_compact_cmd.cpp1
-rw-r--r--src/mongo/s/commands/cluster_write_cmd.cpp26
8 files changed, 161 insertions, 50 deletions
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 33bd413b4f7..9f9c43c079a 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -132,6 +132,7 @@ env.Library(
'$BUILD_DIR/mongo/db/query/command_request_response',
'$BUILD_DIR/mongo/db/query/cursor_response_idl',
'$BUILD_DIR/mongo/db/query/map_reduce_output_format',
+ '$BUILD_DIR/mongo/db/query/query_shape/query_shape',
'$BUILD_DIR/mongo/db/read_write_concern_defaults',
'$BUILD_DIR/mongo/db/repl/hello_auth',
'$BUILD_DIR/mongo/db/repl/hello_command',
@@ -240,6 +241,7 @@ env.CppUnitTest(
"cluster_command_test_fixture.cpp",
"cluster_delete_test.cpp",
"cluster_distinct_test.cpp",
+ "cluster_explain_test.cpp",
"cluster_find_and_modify_test.cpp",
"cluster_find_test.cpp",
"cluster_insert_test.cpp",
diff --git a/src/mongo/s/commands/cluster_db_stats_cmd.cpp b/src/mongo/s/commands/cluster_db_stats_cmd.cpp
index 9154d5f40d8..9439e967521 100644
--- a/src/mongo/s/commands/cluster_db_stats_cmd.cpp
+++ b/src/mongo/s/commands/cluster_db_stats_cmd.cpp
@@ -131,11 +131,12 @@ public:
opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
ReadPreferenceSetting::get(opCtx),
Shard::RetryPolicy::kIdempotent);
- if (!appendRawResponses(opCtx, &errmsg, &output, shardResponses).responseOK) {
+ auto appendResult = appendRawResponses(opCtx, &errmsg, &output, shardResponses);
+ if (!appendResult.responseOK) {
return false;
}
- aggregateResults(scale, shardResponses, output);
+ aggregateResults(scale, appendResult.successResponses, output);
return true;
}
diff --git a/src/mongo/s/commands/cluster_explain.cpp b/src/mongo/s/commands/cluster_explain.cpp
index 759ce8ed3d4..dd85d94d57f 100644
--- a/src/mongo/s/commands/cluster_explain.cpp
+++ b/src/mongo/s/commands/cluster_explain.cpp
@@ -30,7 +30,6 @@
#include "mongo/platform/basic.h"
#include "mongo/bson/bsonmisc.h"
-#include "mongo/db/commands.h"
#include "mongo/db/query/explain_common.h"
#include "mongo/idl/command_generic_argument.h"
#include "mongo/rpc/get_status_from_command_result.h"
@@ -115,18 +114,36 @@ void throwOnBadARSResponse(const AsyncRequestsSender::Response& arsResponse) {
// static
BSONObj ClusterExplain::wrapAsExplain(const BSONObj& cmdObj, ExplainOptions::Verbosity verbosity) {
- auto filtered = CommandHelpers::filterCommandRequestForPassthrough(cmdObj);
BSONObjBuilder out;
- out.append("explain", filtered);
- out.append("verbosity", ExplainOptions::verbosityString(verbosity));
-
- // Propagate all generic arguments out of the inner command since the shards will only process
- // them at the top level.
- for (auto elem : filtered) {
- if (isGenericArgument(elem.fieldNameStringData())) {
- out.append(elem);
+ // Prune generic arguments out of the inner command since any relevant ones should already
+ // be provided to the outer explain command. The shards will only process them at the top level.
+ // As an exception, the "comment" parameter will be propagated out of the inner command to
+ // maintain the behavior in our documentation:
+ // https://www.mongodb.com/docs/manual/reference/command/explain/.
+ // The "readConcern" parameter will also be propagated out of the inner command as the final
+ // explain command inherits readConcern from the inner command invocation.
+ BSONObjBuilder explainBuilder = out.subobjStart("explain");
+ BSONElement commentField;
+ BSONElement readConcernField;
+ for (auto&& elem : cmdObj) {
+ const auto& fieldName = elem.fieldNameStringData();
+ if (!isGenericArgument(fieldName)) {
+ explainBuilder.append(elem);
+ } else if (fieldName == "comment"_sd) {
+ commentField = elem;
+ } else if (fieldName == "readConcern"_sd) {
+ readConcernField = elem;
}
}
+ explainBuilder.done();
+
+ out.append("verbosity", ExplainOptions::verbosityString(verbosity));
+ if (commentField) {
+ out.append(commentField);
+ }
+ if (readConcernField) {
+ out.append(readConcernField);
+ }
return out.obj();
}
diff --git a/src/mongo/s/commands/cluster_explain.h b/src/mongo/s/commands/cluster_explain.h
index f1ff6716de8..9be0ccb960a 100644
--- a/src/mongo/s/commands/cluster_explain.h
+++ b/src/mongo/s/commands/cluster_explain.h
@@ -46,7 +46,8 @@ class ClusterExplain {
public:
/**
* Returns an explain command request wrapping the passed in command at the given verbosity
- * level, propagating generic top-level command arguments.
+ * level, pruning any generic arguments in the inner command as they should already be provided
+ * on the top-level outer commmand.
*/
static BSONObj wrapAsExplain(const BSONObj& cmdObj, ExplainOptions::Verbosity verbosity);
diff --git a/src/mongo/s/commands/cluster_explain_test.cpp b/src/mongo/s/commands/cluster_explain_test.cpp
new file mode 100644
index 00000000000..298ed85ca61
--- /dev/null
+++ b/src/mongo/s/commands/cluster_explain_test.cpp
@@ -0,0 +1,103 @@
+/**
+ * Copyright (C) 2024-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/db/query/explain_verbosity_gen.h"
+#include "mongo/idl/command_generic_argument.h"
+#include "mongo/s/commands/cluster_explain.h"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+namespace {
+void testPruneGenericArgs(const std::string& genericArg) {
+ auto internalCmd = fromjson("{find: 'test', filter: {a: 1}, " + genericArg + "}");
+ auto verbosity = explain::VerbosityEnum::kQueryPlanner;
+ auto expected =
+ fromjson("{explain: {find: 'test', filter: {a: 1}}, verbosity: 'queryPlanner'}");
+ ASSERT_BSONOBJ_EQ(ClusterExplain::wrapAsExplain(internalCmd, verbosity), expected);
+}
+
+void testPropagateGenericArgs(const std::string& genericArg) {
+ auto internalCmd = fromjson("{find: 'test', filter: {a: 1}, " + genericArg + "}");
+ auto verbosity = explain::VerbosityEnum::kQueryPlanner;
+ auto expected = fromjson(
+ "{explain: {find: 'test', filter: {a: 1}}, verbosity: 'queryPlanner', " + genericArg + "}");
+ ASSERT_BSONOBJ_EQ(ClusterExplain::wrapAsExplain(internalCmd, verbosity), expected);
+}
+
+TEST(ClusterExplainTest, PruneMaxTimeMS) {
+ std::string maxTimeMS = "maxTimeMS: 1";
+ testPruneGenericArgs(maxTimeMS);
+}
+
+TEST(ClusterExplainTest, PruneWriteConcern) {
+ std::string writeConcern = "writeConcern: {w: 1}";
+ testPruneGenericArgs(writeConcern);
+}
+
+TEST(ClusterExplainTest, PruneLsid) {
+ auto internalCmd = BSON("find"
+ << "test"
+ << "filter" << BSON("a" << 1) << "lsid"
+ << BSON("id" << mongo::UUID::gen()));
+ auto verbosity = explain::VerbosityEnum::kQueryPlanner;
+ auto expected =
+ fromjson("{explain: {find: 'test', filter: {a: 1}}, verbosity: 'queryPlanner'}");
+ ASSERT_BSONOBJ_EQ(ClusterExplain::wrapAsExplain(internalCmd, verbosity), expected);
+}
+
+TEST(ClusterExplainTest, PruneReadPreference) {
+ std::string readPreference = "$queryOptions: {$readPreference: 'secondary'}";
+ testPruneGenericArgs(readPreference);
+}
+
+TEST(ClusterExplainTest, PruneClusterTime) {
+ auto internalCmd = BSON("find"
+ << "test"
+ << "filter" << BSON("a" << 1) << "$clusterTime"
+ << BSON("clusterTime" << Timestamp(2, 2)) << "$configTime"
+ << Timestamp(2, 2) << "$topologyTime" << Timestamp(2, 2));
+ auto verbosity = explain::VerbosityEnum::kQueryPlanner;
+ auto expected =
+ fromjson("{explain: {find: 'test', filter: {a: 1}}, verbosity: 'queryPlanner'}");
+ ASSERT_BSONOBJ_EQ(ClusterExplain::wrapAsExplain(internalCmd, verbosity), expected);
+}
+
+TEST(ClusterExplainTest, PropagateComment) {
+ std::string comment = "comment: 'Quetzlcoatl'";
+ testPropagateGenericArgs(comment);
+}
+
+TEST(ClusterExplainTest, PropagateReadConcern) {
+ std::string readConcern = "readConcern: {level: 'linearizable'}";
+ testPropagateGenericArgs(readConcern);
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_find_cmd.h b/src/mongo/s/commands/cluster_find_cmd.h
index 7d58f7ab0c4..6476d883200 100644
--- a/src/mongo/s/commands/cluster_find_cmd.h
+++ b/src/mongo/s/commands/cluster_find_cmd.h
@@ -38,6 +38,9 @@
#include "mongo/db/fle_crud.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/query/cursor_response.h"
+#include "mongo/db/query/query_shape/query_shape.h"
+#include "mongo/db/query/query_stats/find_key.h"
+#include "mongo/db/query/query_stats/query_stats.h"
#include "mongo/db/stats/counters.h"
#include "mongo/db/views/resolved_view.h"
#include "mongo/rpc/get_status_from_command_result.h"
@@ -201,16 +204,23 @@ public:
Impl::checkCanRunHere(opCtx);
- auto findCommand = _parseCmdObjectToFindCommandRequest(opCtx, ns(), _request.body);
-
- const boost::intrusive_ptr<ExpressionContext> expCtx;
- auto cq = uassertStatusOK(
- CanonicalQuery::canonicalize(opCtx,
- std::move(findCommand),
- false, /* isExplain */
- expCtx,
- ExtensionsCallbackNoop(),
- MatchExpressionParser::kAllowAllSpecialFeatures));
+ auto&& parsedFindResult = uassertStatusOK(parsed_find_command::parse(
+ opCtx,
+ _parseCmdObjectToFindCommandRequest(opCtx, ns(), _request.body),
+ ExtensionsCallbackNoop(),
+ MatchExpressionParser::kAllowAllSpecialFeatures));
+ auto& expCtx = parsedFindResult.first;
+ auto& parsedFind = parsedFindResult.second;
+
+ if (!_didDoFLERewrite) {
+ query_stats::registerRequest(opCtx, expCtx->ns, [&]() {
+ // This callback is either never invoked or invoked
+ // immediately within registerRequest, so
+ // use-after-move of parsedFind isn't an issue.
+ return std::make_unique<query_stats::FindKey>(expCtx, *parsedFind);
+ });
+ }
+ auto cq = uassertStatusOK(CanonicalQuery::canonicalize(expCtx, std::move(parsedFind)));
try {
// Do the work to generate the first batch of results. This blocks waiting to get
@@ -264,7 +274,7 @@ public:
* were supplied with the command, and sets the constant runtime values that will be
* forwarded to each shard.
*/
- static std::unique_ptr<FindCommandRequest> _parseCmdObjectToFindCommandRequest(
+ std::unique_ptr<FindCommandRequest> _parseCmdObjectToFindCommandRequest(
OperationContext* opCtx, NamespaceString nss, BSONObj cmdObj) {
auto findCommand = query_request_helper::makeFromFindCommand(
std::move(cmdObj),
@@ -291,6 +301,7 @@ public:
invariant(findCommand->getNamespaceOrUUID().nss());
processFLEFindS(
opCtx, findCommand->getNamespaceOrUUID().nss().get(), findCommand.get());
+ _didDoFLERewrite = true;
}
return findCommand;
@@ -298,6 +309,7 @@ public:
const OpMsgRequest& _request;
const StringData _dbName;
+ bool _didDoFLERewrite{false};
};
};
diff --git a/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp b/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp
index b1cecbf555e..7abae202f17 100644
--- a/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp
+++ b/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp
@@ -32,6 +32,7 @@
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
#include "mongo/db/commands/fle2_compact_gen.h"
+#include "mongo/db/curop.h"
#include "mongo/s/cluster_commands_helpers.h"
#include "mongo/s/grid.h"
diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp
index 637ad92085d..8590832acc6 100644
--- a/src/mongo/s/commands/cluster_write_cmd.cpp
+++ b/src/mongo/s/commands/cluster_write_cmd.cpp
@@ -145,11 +145,6 @@ boost::optional<WouldChangeOwningShardInfo> getWouldChangeOwningShardErrorInfo(
void handleWouldChangeOwningShardErrorRetryableWrite(OperationContext* opCtx,
BatchedCommandRequest* request,
BatchedCommandResponse* response) {
- // Strip write concern because this command will be sent as part of a
- // transaction and the write concern has already been loaded onto the opCtx and
- // will be picked up by the transaction API.
- request->unsetWriteConcern();
-
// Strip runtime constants because they will be added again when the API sends this command
// through the service entry point.
request->unsetLegacyRuntimeConstants();
@@ -318,11 +313,6 @@ bool handleWouldChangeOwningShardError(OperationContext* opCtx,
auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
readConcernArgs = repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern);
- // Ensure the retried operation does not include WC inside the transaction. The
- // transaction commit will still use the WC, because it uses the WC from the opCtx
- // (which has been set previously in Strategy).
- request->unsetWriteConcern();
-
documentShardKeyUpdateUtil::startTransactionForShardKeyUpdate(opCtx);
// Clear the error details from the response object before sending the write again
response->unsetErrDetails();
@@ -507,22 +497,6 @@ bool ClusterWriteCmd::InvocationBase::runImpl(OperationContext* opCtx,
BatchWriteExecStats stats;
BatchedCommandResponse response;
- // Append the write concern from the opCtx extracted during command setup.
- if (!batchedRequest.hasWriteConcern()) {
- batchedRequest.setWriteConcern(opCtx->getWriteConcern().toBSON());
- }
-
- // Write ops are never allowed to have writeConcern inside transactions. Normally
- // disallowing WC on non-terminal commands in a transaction is handled earlier, during
- // command dispatch. However, if this is a regular write operation being automatically
- // retried inside a transaction (such as changing a document's shard key across shards),
- // then batchedRequest will have a writeConcern (added by the if() above) from when it was
- // initially run outside a transaction. Thus it's necessary to unconditionally clear the
- // writeConcern when in a transaction.
- if (TransactionRouter::get(opCtx)) {
- batchedRequest.unsetWriteConcern();
- }
-
cluster::write(opCtx, batchedRequest, &stats, &response);
bool updatedShardKey = false;