SERVER-102869: Make $mergeCursors internal (#34111)

GitOrigin-RevId: 8614d4d8831b08ea0f0156fdb6646a21bed348ec
This commit is contained in:
Serhii Lysenko 2025-03-27 18:27:01 +00:00 committed by MongoDB Bot
parent 01446737d1
commit 6cf88233f0
5 changed files with 72 additions and 45 deletions

View File

@ -32,7 +32,7 @@ const testInternalClient = (function createInternalClient() {
const curDB = testInternalClient.getDB(dbName);
// Tests that the internal stage '$mergeCursors' does not throw 'ApiStrictError' with an internal
// Tests that the internal stage '$mergeCursors' is allowed in requests with an internal
// client and 'apiStrict' set to true.
let result = curDB.runCommand({
aggregate: collName,
@ -52,8 +52,8 @@ let result = curDB.runCommand({
});
assert.commandWorked(result);
// Tests that the internal stage '$mergeCursors' throws 'ApiStrictError' with default external
// client when 'apiStrict' is set to true.
// Tests that the internal stage '$mergeCursors' is not allowed in user requests with default
// external client when 'apiStrict' is set to true.
result = testDB.runCommand({
aggregate: collName,
pipeline: [{
@ -70,9 +70,9 @@ result = testDB.runCommand({
apiVersion: "1",
apiStrict: true
});
assert.commandFailedWithCode(result, ErrorCodes.APIStrictError);
assert.commandFailedWithCode(result, 5491300);
// Tests that the internal stage '$mergeCursors' should not fail with 'ApiStrictError' with default
// Tests that the internal stage '$mergeCursors' is not allowed with default
// external client without specifying 'apiStrict' flag.
result = testDB.runCommand({
aggregate: collName,
@ -89,7 +89,7 @@ result = testDB.runCommand({
writeConcern: {w: "majority"},
apiVersion: "1"
});
assert.commandWorked(result);
assert.commandFailedWithCode(result, 5491300);
// Tests that the 'exchange' option cannot be specified by external client with 'apiStrict' set to
// true.

View File

@ -1,35 +0,0 @@
/**
* SERVER-95350: Fix jstests/aggregation/api_version_stage_allowance_checks.js.
*/
(function() {
"use strict";
load("jstests/libs/collection_drop_recreate.js"); // For assertDropAndRecreateCollection.
const collName = 'test';
assertDropAndRecreateCollection(db, collName);
let docs = [];
for (let i = 0; i < 10; ++i) {
docs.push({x: i, y: i, z: i});
}
db[collName].insertMany(docs);
assert.commandWorked(db.runCommand({
explain: {
aggregate: collName,
pipeline: [{
$mergeCursors: {
sort: {y: 1, z: 1},
compareWholeSortKey: false,
remotes: [],
nss: "test.mergeCursors",
allowPartialResults: false,
}
}],
cursor: {},
readConcern: {},
},
}));
}());

View File

@ -7053,6 +7053,42 @@ export const authCommandsLib = {
},
]
},
{
testname: "aggregate_$mergeCursors",
command: {
aggregate: "foo",
pipeline: [{
$mergeCursors: {
sort: {y: 1, z: 1},
compareWholeSortKey: false,
remotes: [],
nss: "test.mergeCursors",
allowPartialResults: false,
}
}],
cursor: {},
},
testcases: [
{
runOnDb: firstDbName,
roles: {__system: 1},
// $mergeCursors requires __system role OR a user with internal and find action types as privileges.
expectFail: true,
privileges: [
{resource: {cluster: true}, actions: ["internal"]},
{resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
],
},
{
runOnDb: firstDbName,
// Find action type as a privilege alone is not sufficient for $mergeCursors.
expectAuthzFailure: true,
privileges: [
{resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
],
},
],
},
{
testname: "validate_db_metadata_command_specific_db",
command: {

View File

@ -288,6 +288,32 @@ public:
}
};
class LiteParsedDocumentSourceInternal final : public LiteParsedDocumentSource {
public:
/**
* Creates the default LiteParsedDocumentSource for internal document sources. This requires
* the privilege on 'internal' action. This should still be used with caution. Make sure your
* stage doesn't need to communicate any special behavior before registering a DocumentSource
* using this parser.
*/
static std::unique_ptr<LiteParsedDocumentSourceInternal> parse(const NamespaceString& nss,
const BSONElement& spec) {
return std::make_unique<LiteParsedDocumentSourceInternal>(spec.fieldName());
}
LiteParsedDocumentSourceInternal(std::string parseTimeName)
: LiteParsedDocumentSource(std::move(parseTimeName)) {}
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
return stdx::unordered_set<NamespaceString>();
}
PrivilegeVector requiredPrivileges(bool isMongos, bool bypassDocumentValidation) const final {
return {Privilege(ResourcePattern::forClusterResource(), ActionSet{ActionType::internal})};
}
};
/**
* Helper class for DocumentSources which reference a foreign collection.
*/

View File

@ -38,10 +38,10 @@
namespace mongo {
REGISTER_DOCUMENT_SOURCE(mergeCursors,
LiteParsedDocumentSourceDefault::parse,
REGISTER_INTERNAL_DOCUMENT_SOURCE(mergeCursors,
LiteParsedDocumentSourceInternal::parse,
DocumentSourceMergeCursors::createFromBson,
AllowedWithApiStrict::kInternal);
true);
constexpr StringData DocumentSourceMergeCursors::kStageName;