summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Noma <gregory.noma@gmail.com>2022-01-12 15:42:07 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-01-12 16:38:00 +0000
commiteb75b6ccc62f7c8ea26a57c1b5eb96a41809396a (patch)
tree854e337b4437f11d54b40690f18728f44050e700
parent36e150219f44b029289135099236b328f3ed432a (diff)
downloadmongo-eb75b6ccc62f7c8ea26a57c1b5eb96a41809396a.tar.gz
SERVER-62443 Add `collectionUUID` parameter to `find` command
-rw-r--r--jstests/core/collection_uuid_find.js56
-rw-r--r--jstests/libs/parallelTester.js6
-rw-r--r--jstests/noPassthroughWithMongod/collection_uuid_find_by_uuid.js18
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/find_cmd.cpp18
-rw-r--r--src/mongo/db/query/find_command.idl5
6 files changed, 103 insertions, 1 deletions
diff --git a/jstests/core/collection_uuid_find.js b/jstests/core/collection_uuid_find.js
new file mode 100644
index 00000000000..e958bffa890
--- /dev/null
+++ b/jstests/core/collection_uuid_find.js
@@ -0,0 +1,56 @@
+/**
+ * Tests the collectionUUID parameter of the find command.
+ *
+ * @tags: [
+ * featureFlagCommandsAcceptCollectionUUID,
+ * tenant_migration_incompatible,
+ * ]
+ */
+(function() {
+'use strict';
+
+const testDB = db.getSiblingDB(jsTestName());
+assert.commandWorked(testDB.dropDatabase());
+
+const coll = testDB['coll'];
+assert.commandWorked(coll.insert({_id: 0}));
+
+const uuid =
+ assert.commandWorked(testDB.runCommand({listCollections: 1})).cursor.firstBatch[0].info.uuid;
+
+// The command succeeds when the correct UUID is provided.
+assert.commandWorked(testDB.runCommand({find: coll.getName(), collectionUUID: uuid}));
+
+// The command fails when the provided UUID does not correspond to an existing collection.
+const nonexistentUUID = UUID();
+let res = assert.commandFailedWithCode(
+ testDB.runCommand({find: coll.getName(), collectionUUID: nonexistentUUID}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, nonexistentUUID);
+assert.eq(res.actualNamespace, "");
+
+// The command fails when the provided UUID corresponds to a different collection.
+const coll2 = testDB['coll_2'];
+assert.commandWorked(coll2.insert({_id: 1}));
+res = assert.commandFailedWithCode(testDB.runCommand({find: coll2.getName(), collectionUUID: uuid}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid);
+assert.eq(res.actualNamespace, coll.getFullName());
+
+// The command fails when the provided UUID corresponds to a different collection, even if the
+// provided namespace does not exist.
+coll2.drop();
+res = assert.commandFailedWithCode(testDB.runCommand({find: coll2.getName(), collectionUUID: uuid}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid);
+assert.eq(res.actualNamespace, coll.getFullName());
+
+// The command fails when the provided UUID corresponds to a different collection, even if the
+// provided namespace is a view.
+const view = db['view'];
+assert.commandWorked(testDB.createView(view.getName(), coll.getName(), []));
+res = assert.commandFailedWithCode(testDB.runCommand({find: view.getName(), collectionUUID: uuid}),
+ ErrorCodes.CollectionUUIDMismatch);
+assert.eq(res.collectionUUID, uuid);
+assert.eq(res.actualNamespace, coll.getFullName());
+})();
diff --git a/jstests/libs/parallelTester.js b/jstests/libs/parallelTester.js
index 7f4365aa798..67859ae65e3 100644
--- a/jstests/libs/parallelTester.js
+++ b/jstests/libs/parallelTester.js
@@ -230,7 +230,11 @@ if (typeof _threadInject != "undefined") {
"timeseries/timeseries_delete_hint.js",
"timeseries/timeseries_update_hint.js",
"timeseries/timeseries_delete_concurrent.js",
- "timeseries/timeseries_update_concurrent.js"
+ "timeseries/timeseries_update_concurrent.js",
+
+ // TODO (SERVER-60185): Remove the collection_uuid_*.js exclusions once the feature flag
+ // is enabled by default.
+ "collection_uuid_find.js",
]);
// Get files, including files in subdirectories.
diff --git a/jstests/noPassthroughWithMongod/collection_uuid_find_by_uuid.js b/jstests/noPassthroughWithMongod/collection_uuid_find_by_uuid.js
new file mode 100644
index 00000000000..26a4f56e135
--- /dev/null
+++ b/jstests/noPassthroughWithMongod/collection_uuid_find_by_uuid.js
@@ -0,0 +1,18 @@
+/**
+ * Tests that when running the find command by UUID, the collectionUUID parameter cannot also be
+ * specified.
+ */
+(function() {
+'use strict';
+
+const coll = db[jsTestName()];
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0}));
+
+const uuid = assert.commandWorked(db.runCommand({listCollections: 1}))
+ .cursor.firstBatch.find(c => c.name === coll.getName())
+ .info.uuid;
+
+assert.commandFailedWithCode(db.runCommand({find: uuid, collectionUUID: uuid}),
+ ErrorCodes.InvalidOptions);
+})();
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index c8e94e111c9..587aaf45c24 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -355,6 +355,7 @@ env.Library(
'$BUILD_DIR/mongo/db/catalog/catalog_helpers',
'$BUILD_DIR/mongo/db/catalog/collection_catalog_helper',
'$BUILD_DIR/mongo/db/catalog/collection_query_info',
+ '$BUILD_DIR/mongo/db/catalog/collection_uuid_mismatch',
'$BUILD_DIR/mongo/db/catalog/collection_validation',
'$BUILD_DIR/mongo/db/catalog/database_holder',
'$BUILD_DIR/mongo/db/catalog/index_key_validate',
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index f0eeb4fe30d..3ab8d7d8d6a 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -33,6 +33,7 @@
#include "mongo/db/auth/authorization_checks.h"
#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/catalog/collection_uuid_mismatch.h"
#include "mongo/db/client.h"
#include "mongo/db/clientcursor.h"
#include "mongo/db/commands.h"
@@ -400,6 +401,11 @@ public:
!(repl::ReadConcernArgs::get(opCtx).isSpeculativeMajority() &&
!findCommand->getAllowSpeculativeMajorityRead()));
+ uassert(ErrorCodes::InvalidOptions,
+ "When using the find command by UUID, the collectionUUID parameter cannot also "
+ "be specified",
+ !findCommand->getNamespaceOrUUID().uuid() || !findCommand->getCollectionUUID());
+
auto replCoord = repl::ReplicationCoordinator::get(opCtx);
const auto txnParticipant = TransactionParticipant::get(opCtx);
uassert(ErrorCodes::InvalidOptions,
@@ -472,6 +478,18 @@ public:
<< " specified in query request not found",
ctx || !findCommand->getNamespaceOrUUID().uuid());
+ uassert(ErrorCodes::InvalidOptions,
+ "The collectionUUID parameter is not enabled",
+ !findCommand->getCollectionUUID() ||
+ feature_flags::gCommandsAcceptCollectionUUID.isEnabled(
+ serverGlobalParams.featureCompatibility));
+
+ if (findCommand->getCollectionUUID() &&
+ (!ctx->getCollection() ||
+ findCommand->getCollectionUUID() != ctx->getCollection()->uuid())) {
+ uassertCollectionUUIDMismatch(opCtx, *findCommand->getCollectionUUID());
+ }
+
// Set the namespace if a collection was found, as opposed to nothing or a view.
if (ctx) {
query_request_helper::refreshNSS(ctx->getNss(), findCommand.get());
diff --git a/src/mongo/db/query/find_command.idl b/src/mongo/db/query/find_command.idl
index 2760dcb9c2b..bb89213f4aa 100644
--- a/src/mongo/db/query/find_command.idl
+++ b/src/mongo/db/query/find_command.idl
@@ -239,3 +239,8 @@ commands:
type: LegacyRuntimeConstants
optional: true
unstable: true
+ collectionUUID:
+ description: "The expected UUID of the collection."
+ type: uuid
+ optional: true
+ unstable: true