summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosef Ahmad <josef.ahmad@mongodb.com>2022-09-27 12:04:59 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-09-27 13:07:16 +0000
commit2115bfcb0ae08ca1dfb1cbd6cfc95974b4f11240 (patch)
tree7364d7b098e74800697775e97892b5a295e10e91
parent1ab9c22972efa05aa1f54f25c853b00aaa33455f (diff)
downloadmongo-2115bfcb0ae08ca1dfb1cbd6cfc95974b4f11240.tar.gz
SERVER-67895 Add _shardsvrWriteGlobalIndexKeys command
-rw-r--r--jstests/auth/lib/commands_lib.js21
-rw-r--r--jstests/core/views/views_all_commands.js1
-rw-r--r--jstests/noPassthrough/shardsvr_global_index_crud_bulk.js649
-rw-r--r--jstests/replsets/all_commands_downgrading_to_upgraded.js1
-rw-r--r--jstests/replsets/db_reads_while_recovering_all_commands.js1
-rw-r--r--jstests/replsets/global_index_ddl_rollback.js (renamed from jstests/replsets/shardsvr_global_index_ddl_rollback.js)0
-rw-r--r--jstests/replsets/shardsvr_global_index_crud_rollback.js1
-rw-r--r--jstests/sharding/read_write_concern_defaults_application.js1
-rw-r--r--src/mongo/db/global_index.cpp2
-rw-r--r--src/mongo/db/s/SConscript1
-rw-r--r--src/mongo/db/s/global_index/global_index_cloning_service_test.cpp10
-rw-r--r--src/mongo/db/s/global_index/global_index_inserter.cpp4
-rw-r--r--src/mongo/db/s/global_index_crud_commands.idl43
-rw-r--r--src/mongo/db/s/shardsvr_create_global_index_command.cpp10
-rw-r--r--src/mongo/db/s/shardsvr_delete_global_index_key_command.cpp9
-rw-r--r--src/mongo/db/s/shardsvr_drop_global_index_command.cpp13
-rw-r--r--src/mongo/db/s/shardsvr_insert_global_index_key_command.cpp9
-rw-r--r--src/mongo/db/s/shardsvr_write_global_index_keys_command.cpp115
18 files changed, 848 insertions, 43 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 12f2774c306..f868ecd612f 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -2839,6 +2839,27 @@ var authCommandsLib = {
]
},
{
+ testname: "_shardsvrWriteGlobalIndexKeys",
+ command: {_shardsvrWriteGlobalIndexKeys: 1, ops: [
+ {_shardsvrInsertGlobalIndexKey: UUID(), key: {a: 1}, docKey: {shk: 1, _id: 1}},
+ {_shardsvrDeleteGlobalIndexKey: UUID(), key: {a: 1}, docKey: {shk: 1, _id: 1}},
+ ]},
+ skipSharded: true,
+ testcases: [
+ {
+ runOnDb: adminDbName,
+ roles: {__system: 1},
+ privileges: [{resource: {cluster: true}, actions: ["internal"]}],
+ expectFail: true
+ },
+ {runOnDb: firstDbName,
+ roles: {__system: 1},
+ privileges: [{resource: {cluster: true}, actions: ["internal"]}],
+ expectFail: true
+ },
+ ]
+ },
+ {
testname: "commitTxn",
command: {commitTransaction: 1},
// TODO (SERVER-53497): Enable auth testing for abortTransaction and commitTransaction.
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js
index 0f7b6e04869..63a1d8242c4 100644
--- a/jstests/core/views/views_all_commands.js
+++ b/jstests/core/views/views_all_commands.js
@@ -150,6 +150,7 @@ let viewsCommandTests = {
_shardsvrDropIndexes: {skip: isAnInternalCommand},
_shardsvrInsertGlobalIndexKey: {skip: isAnInternalCommand},
_shardsvrDeleteGlobalIndexKey: {skip: isAnInternalCommand},
+ _shardsvrWriteGlobalIndexKeys: {skip: isAnInternalCommand},
_shardsvrCleanupReshardCollection: {skip: isAnInternalCommand},
_shardsvrRegisterIndex: {skip: isAnInternalCommand},
_shardsvrCommitIndexParticipant: {skip: isAnInternalCommand},
diff --git a/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js b/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js
new file mode 100644
index 00000000000..2d5976d777b
--- /dev/null
+++ b/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js
@@ -0,0 +1,649 @@
+/**
+ * Tests _shardsvrWriteGlobalIndexKeys, which is sometimes referred to as 'bulk write' as it runs
+ * multiple _shardsvrInsertGlobalIndexKey and _shardsvrDeleteGlobalIndexKey statements in bulk.
+ *
+ * @tags: [
+ * featureFlagGlobalIndexes,
+ * requires_fcv_62,
+ * requires_replication,
+ * ]
+ */
+
+(function() {
+"use strict";
+
+function uuidToString(uuid) {
+ const [_, uuidString] = uuid.toString().match(/"((?:\\.|[^"\\])*)"/);
+ return uuidString;
+}
+
+function entriesInContainer(primary, uuid) {
+ return primary.getDB("system")
+ .getCollection("globalIndexes." + uuidToString(uuid))
+ .find()
+ .itcount();
+}
+
+// Speed up test time.
+const maxNumberOfTxnOpsInSingleOplogEntry = 1000;
+
+const rst = new ReplSetTest({
+ nodes: 2,
+ nodeOptions: {
+ setParameter: {
+ maxNumberOfTransactionOperationsInSingleOplogEntry: maxNumberOfTxnOpsInSingleOplogEntry
+ }
+ }
+});
+rst.startSet();
+rst.initiate();
+const primary = rst.getPrimary();
+const adminDB = primary.getDB("admin");
+const globalIndexUUID = UUID();
+const session = primary.startSession();
+
+assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexUUID}));
+assert.eq(0, entriesInContainer(primary, globalIndexUUID));
+
+// _shardsvrWriteGlobalIndexKeys must run inside a transaction.
+assert.commandFailedWithCode(adminDB.runCommand({_shardsvrWriteGlobalIndexKeys: 1, ops: []}),
+ 6789500);
+
+// Missing required '_shardsvrWriteGlobalIndexKeys.ops' field.
+{
+ session.startTransaction();
+ assert.commandFailedWithCode(
+ session.getDatabase("test").runCommand({_shardsvrWriteGlobalIndexKeys: 1}), 40414);
+ session.abortTransaction();
+}
+
+// _shardsvrWriteGlobalIndexKeys requires at least one statement in the 'ops' array.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandFailedWithCode(
+ session.getDatabase("test").runCommand({_shardsvrWriteGlobalIndexKeys: 1, ops: []}),
+ 6789502);
+ session.abortTransaction();
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+
+ assert.eq(containerEntriesAfter, containerEntriesBefore);
+}
+
+// Only _shardsvrInsertGlobalIndexKey and _shardsvrDeleteGlobalIndexKey commands are allowed.
+{
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand(
+ {_shardsvrWriteGlobalIndexKeys: 1, ops: [{otherCommand: 1}]}),
+ 6789501);
+ session.abortTransaction();
+}
+
+// Invalid UUID in _shardsvrWriteGlobalIndexKeys statement.
+{
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ "_shardsvrInsertGlobalIndexKey": "not_a_uuid",
+ key: {a: "hola"},
+ docKey: {sk: 3, _id: 3}
+ }]
+ }),
+ ErrorCodes.InvalidUUID);
+ session.abortTransaction();
+}
+
+// Unexisting UUID in statement.
+{
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{"_shardsvrInsertGlobalIndexKey": UUID(), key: {a: "hola"}, docKey: {sk: 3, _id: 3}}]
+ }),
+ 6789402);
+ session.abortTransaction();
+}
+
+// Missing 'key' field in statement.
+{
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{"_shardsvrInsertGlobalIndexKey": globalIndexUUID, docKey: {sk: 3, _id: 3}}]
+ }),
+ 40414);
+ session.abortTransaction();
+}
+
+// Missing 'docKey' field in statement.
+{
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{"_shardsvrInsertGlobalIndexKey": globalIndexUUID, key: {a: "hola"}}]
+ }),
+ 40414);
+ session.abortTransaction();
+}
+
+// Bulk insert a single entry, then bulk delete it.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "abc"},
+ docKey: {s: 123, _id: 987}
+ }]
+ }));
+ session.commitTransaction();
+
+ const containerEntriesAfterInsert = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfterInsert, containerEntriesBefore + 1);
+
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "abc"},
+ docKey: {s: 123, _id: 987}
+ }]
+ }));
+ session.commitTransaction();
+
+ const containerEntriesAfterDelete = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfterDelete, containerEntriesBefore);
+}
+
+// Update an index key.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "the quick brown fox"},
+ docKey: {myShardKey0: "jumped over", myShardKey1: "the lazy dog", _id: 1234567890}
+ }]
+ }));
+ session.commitTransaction();
+
+ const containerEntriesAfterInsert = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfterInsert, containerEntriesBefore + 1);
+
+ // We can't reinsert that same key, as that'll trigger a duplicate key error.
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "the quick brown fox"},
+ docKey: {myShardKey0: "another", myShardKey1: "value", _id: 0}
+ }]
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+
+ // Update the key.
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "the quick brown fox"},
+ docKey: {myShardKey0: "jumped over", myShardKey1: "the lazy dog", _id: 1234567890}
+ },
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "The Quick Brown Fox"},
+ docKey: {myShardKey0: "jumped over", myShardKey1: "the lazy dog", _id: 1234567890}
+ }
+ ]
+ }));
+ session.commitTransaction();
+
+ const containerEntriesAfterUpdate = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfterInsert, containerEntriesAfterUpdate);
+
+ // Verify that the index key update succeeded: we can re-insert the old key, we get a duplicate
+ // key error if reinsterting the updated key.
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "the quick brown fox"},
+ docKey: {myShardKey0: "another", myShardKey1: "shardKeyValue", _id: 0}
+ },
+ ]
+ }));
+ session.commitTransaction();
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "The Quick Brown Fox"},
+ docKey: {myShardKey0: "jumped over", myShardKey1: "the lazy dog", _id: 1234567890}
+ }]
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+}
+
+// A duplicate key error rolls back the whole bulk write.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [{
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "testDupes"},
+ docKey: {myShardKey0: "test", _id: "dupes"}
+ }]
+ }));
+ session.commitTransaction();
+
+ const containerEntriesAfterInsert = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfterInsert, containerEntriesBefore + 1);
+
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "new entry"},
+ docKey: {myShardKey0: "new", _id: "entry"}
+ },
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "testDupes"},
+ docKey: {myShardKey0: "test", _id: "dupes"}
+ }
+ ]
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+
+ const containerEntriesAfterDupKeyError = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfterInsert, containerEntriesAfterDupKeyError);
+}
+
+// A KeyNotFound error rolls back the whole bulk write.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "key that will be rolled back"},
+ docKey: {myShardKey0: "key", _id: "roll back"}
+ },
+ {
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "key that doesn't exist"},
+ docKey: {myShardKey0: "does not", _id: "exist"}
+ }
+ ]
+ }),
+ ErrorCodes.KeyNotFound);
+ session.abortTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesBefore, containerEntriesAfter);
+}
+
+// A KeyNotFound error on the docKey rolls back the whole bulk write.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "key that will be rolled back"},
+ docKey: {myShardKey0: "key", _id: "roll back"}
+ },
+ {
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "key that will be rolled back"},
+ docKey: {myShardKey0: "docKey", _id: "doesn't match"}
+ }
+ ]
+ }),
+ ErrorCodes.KeyNotFound);
+ session.abortTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesBefore, containerEntriesAfter);
+}
+
+// Insert 2k index entries in bulk.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+ let ops = [];
+ for (let i = 0; i < 2000; i++) {
+ ops.push(
+ {_shardsvrInsertGlobalIndexKey: globalIndexUUID, key: {a: i}, docKey: {sk: i, _id: i}});
+ }
+
+ session.startTransaction();
+ assert.commandWorked(
+ session.getDatabase("test").runCommand({_shardsvrWriteGlobalIndexKeys: 1, ops: ops}));
+ session.commitTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore + 2000);
+}
+
+// Delete the 2k index entries above in bulk.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ let ops = [];
+ for (let i = 0; i < 2000; i++) {
+ ops.push(
+ {_shardsvrDeleteGlobalIndexKey: globalIndexUUID, key: {a: i}, docKey: {sk: i, _id: i}});
+ }
+
+ session.startTransaction();
+ assert.commandWorked(
+ session.getDatabase("test").runCommand({_shardsvrWriteGlobalIndexKeys: 1, ops: ops}));
+ session.commitTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore - 2000);
+}
+
+// Insert and delete the same 2k entries in the same bulk.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+ let ops = [];
+ for (let i = 0; i < 2000; i++) {
+ ops.push(
+ {_shardsvrInsertGlobalIndexKey: globalIndexUUID, key: {a: i}, docKey: {sk: i, _id: i}});
+ ops.push(
+ {_shardsvrDeleteGlobalIndexKey: globalIndexUUID, key: {a: i}, docKey: {sk: i, _id: i}});
+ }
+
+ session.startTransaction();
+ assert.commandWorked(
+ session.getDatabase("test").runCommand({_shardsvrWriteGlobalIndexKeys: 1, ops: ops}));
+ session.commitTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore);
+}
+
+// Insert and delete keys on multiple global index containers.
+{
+ const otherGlobalIndexUUID = UUID();
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: otherGlobalIndexUUID}));
+ assert.eq(0, entriesInContainer(primary, otherGlobalIndexUUID));
+
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+ const otherContainerEntriesBefore = entriesInContainer(primary, otherGlobalIndexUUID);
+
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "first"},
+ docKey: {sk0: "first", _id: "first"}
+ },
+ {
+ _shardsvrInsertGlobalIndexKey: otherGlobalIndexUUID,
+ key: {k0: "firstOnSecondContainer"},
+ docKey: {sk0: "firstOnSecondContainer", _id: "firstOnSecondContainer"}
+ },
+ {
+ _shardsvrInsertGlobalIndexKey: otherGlobalIndexUUID,
+ key: {k0: "secondOnSecondContainer"},
+ docKey: {sk0: "secondOnSecondContainer", _id: "secondOnSecondContainer"}
+ },
+ {
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {k0: "first"},
+ docKey: {sk0: "first", _id: "first"}
+ },
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "firstUpdated"},
+ docKey: {sk0: "first", _id: "first"}
+ },
+ {
+ _shardsvrDeleteGlobalIndexKey: otherGlobalIndexUUID,
+ key: {k0: "firstOnSecondContainer"},
+ docKey: {sk0: "firstOnSecondContainer", _id: "firstOnSecondContainer"}
+ },
+ ]
+ }));
+ session.commitTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ const otherContainerEntriesAfter = entriesInContainer(primary, otherGlobalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore + 1);
+ assert.eq(otherContainerEntriesAfter, otherContainerEntriesBefore + 1);
+
+ // Verify that that the expected index keys are in place by fetching by document key, and by
+ // triggering DuplicateKey error on inserting the same index key.
+ assert.eq(1,
+ primary.getDB("system")
+ .getCollection("globalIndexes." + uuidToString(globalIndexUUID))
+ .find({_id: {sk0: "first", _id: "first"}})
+ .itcount());
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "firstUpdated"},
+ docKey: {sk0: "doesn't", _id: "matter"}
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+ assert.eq(1,
+ primary.getDB("system")
+ .getCollection("globalIndexes." + uuidToString(otherGlobalIndexUUID))
+ .find({_id: {sk0: "secondOnSecondContainer", _id: "secondOnSecondContainer"}})
+ .itcount());
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrInsertGlobalIndexKey: otherGlobalIndexUUID,
+ key: {k0: "secondOnSecondContainer"},
+ docKey: {sk0: "doesn't", _id: "matter"}
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+}
+
+// Insert and delete keys in bulk while inserting a document to a collection in the same multi-doc
+// transaction.
+{
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "globalIndexKey"},
+ docKey: {sk0: "globalIndexKey", _id: "globalIndexKey"}
+ },
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "globalIndexKey2"},
+ docKey: {sk0: "globalIndexKey2", _id: "globalIndexKey"}
+ },
+ ]
+ }));
+ assert.commandWorked(session.getDatabase("test").getCollection("c").insertOne({x: "data"}));
+ session.commitTransaction();
+
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore + 2);
+
+ // Verify that that the expected index keys are in place by fetching by document key, and by
+ // triggering DuplicateKey error on inserting the same index key.
+ assert.eq(1,
+ primary.getDB("system")
+ .getCollection("globalIndexes." + uuidToString(globalIndexUUID))
+ .find({_id: {sk0: "globalIndexKey", _id: "globalIndexKey"}})
+ .itcount());
+ assert.eq(1,
+ primary.getDB("system")
+ .getCollection("globalIndexes." + uuidToString(globalIndexUUID))
+ .find({_id: {sk0: "globalIndexKey2", _id: "globalIndexKey"}})
+ .itcount());
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "globalIndexKey"},
+ docKey: {sk0: "doesn't", _id: "matter"}
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+ session.startTransaction();
+ assert.commandFailedWithCode(session.getDatabase("test").runCommand({
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {k0: "globalIndexKey2"},
+ docKey: {sk0: "doesn't", _id: "matter"}
+ }),
+ ErrorCodes.DuplicateKey);
+ session.abortTransaction();
+ assert.eq(1, primary.getDB("test").getCollection("c").find({x: "data"}).itcount());
+}
+
+// A bulk write broken down into multiple applyOps.
+{
+ const uuid = UUID();
+ // Generate three applyOps entries, the last one only containing a single statement.
+ const stmts = 2 * maxNumberOfTxnOpsInSingleOplogEntry + 1;
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+ assert.eq(0, entriesInContainer(primary, uuid));
+
+ let ops = [];
+ for (let i = 0; i < stmts; i++) {
+ ops.push({_shardsvrInsertGlobalIndexKey: uuid, key: {a: i}, docKey: {sk: i, _id: i}});
+ }
+ session.startTransaction();
+ assert.commandWorked(
+ session.getDatabase("test").runCommand({_shardsvrWriteGlobalIndexKeys: 1, ops: ops}));
+ session.commitTransaction();
+
+ // The global index container consists of 'stmts' entries.
+ assert.eq(stmts, entriesInContainer(primary, uuid));
+
+ // The transaction is split into three applyOps entries, with 'stmts' entries in total.
+ const applyOpsSummary =
+ adminDB.getSiblingDB('local')
+ .oplog.rs
+ .aggregate([
+ {$match: {ns: "admin.$cmd", 'o.applyOps.op': 'xi', 'o.applyOps.ui': uuid}},
+ {$project: {count: {$size: '$o.applyOps'}}},
+ ])
+ .toArray();
+ assert.eq(3, applyOpsSummary.length);
+ assert.eq(maxNumberOfTxnOpsInSingleOplogEntry, applyOpsSummary[0]["count"]);
+ assert.eq(maxNumberOfTxnOpsInSingleOplogEntry, applyOpsSummary[1]["count"]);
+ assert.eq(1, applyOpsSummary[2]["count"]);
+ assert.eq(
+ stmts,
+ applyOpsSummary[0]["count"] + applyOpsSummary[1]["count"] + applyOpsSummary[2]["count"]);
+}
+
+// Global index CRUD ops generate the same oplog entry, regardless of whether the transaction
+// statements are bulked together. Also validates the oplog entry.
+{
+ {
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrWriteGlobalIndexKeys: 1,
+ ops: [
+ {
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "insertAndRemove"},
+ docKey: {shardKey: "insert", _id: "andRemove"}
+ },
+ {
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "insertAndRemove"},
+ docKey: {shardKey: "insert", _id: "andRemove"}
+ }
+ ]
+ }));
+ session.commitTransaction();
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore);
+ }
+ {
+ const containerEntriesBefore = entriesInContainer(primary, globalIndexUUID);
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrInsertGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "insertAndRemove"},
+ docKey: {shardKey: "insert", _id: "andRemove"}
+ }));
+ assert.commandWorked(session.getDatabase("test").runCommand({
+ _shardsvrDeleteGlobalIndexKey: globalIndexUUID,
+ key: {myKey: "insertAndRemove"},
+ docKey: {shardKey: "insert", _id: "andRemove"}
+ }));
+ session.commitTransaction();
+ const containerEntriesAfter = entriesInContainer(primary, globalIndexUUID);
+ assert.eq(containerEntriesAfter, containerEntriesBefore);
+ }
+
+ const oplogEntries =
+ adminDB.getSiblingDB('local')
+ .oplog.rs
+ .find({ns: "admin.$cmd", 'o.applyOps.op': 'xi', 'o.applyOps.ui': globalIndexUUID})
+ .sort({$natural: -1})
+ .limit(2)
+ .toArray();
+
+ let oplogEntryBulk = oplogEntries[0];
+ let oplogEntryPlain = oplogEntries[1];
+ assert.neq(oplogEntryBulk["txnNumber"], oplogEntryPlain["txnNumber"]);
+ assert.neq(oplogEntryBulk["ts"], oplogEntryPlain["ts"]);
+ assert.neq(oplogEntryBulk["wall"], oplogEntryPlain["wall"]);
+ delete oplogEntryBulk.ts;
+ delete oplogEntryPlain.ts;
+ delete oplogEntryBulk.txnNumber;
+ delete oplogEntryPlain.txnNumber;
+ delete oplogEntryBulk.wall;
+ delete oplogEntryPlain.wall;
+ assert.docEq(oplogEntryBulk, oplogEntryPlain);
+ assert.eq(oplogEntryBulk["o"]["applyOps"][0]["op"], "xi");
+ assert.docEq(oplogEntryBulk["o"]["applyOps"][0]["o"]["key"], {myKey: "insertAndRemove"});
+ assert.docEq(oplogEntryBulk["o"]["applyOps"][0]["o"]["docKey"],
+ {shardKey: "insert", _id: "andRemove"});
+ assert.eq(oplogEntryBulk["o"]["applyOps"][1]["op"], "xd");
+ assert.docEq(oplogEntryBulk["o"]["applyOps"][1]["o"]["key"], {myKey: "insertAndRemove"});
+ assert.docEq(oplogEntryBulk["o"]["applyOps"][1]["o"]["docKey"],
+ {shardKey: "insert", _id: "andRemove"});
+}
+
+session.endSession();
+rst.stopSet();
+})();
diff --git a/jstests/replsets/all_commands_downgrading_to_upgraded.js b/jstests/replsets/all_commands_downgrading_to_upgraded.js
index 9c16dfec889..3d91bcf6c24 100644
--- a/jstests/replsets/all_commands_downgrading_to_upgraded.js
+++ b/jstests/replsets/all_commands_downgrading_to_upgraded.js
@@ -112,6 +112,7 @@ const allCommands = {
_shardsvrGetStatsForBalancing: {skip: isAnInternalCommand},
_shardsvrInsertGlobalIndexKey: {skip: isAnInternalCommand},
_shardsvrDeleteGlobalIndexKey: {skip: isAnInternalCommand},
+ _shardsvrWriteGlobalIndexKeys: {skip: isAnInternalCommand},
_shardsvrJoinMigrations: {skip: isAnInternalCommand},
_shardsvrMovePrimary: {skip: isAnInternalCommand},
_shardsvrMoveRange: {skip: isAnInternalCommand},
diff --git a/jstests/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js
index b17ed68ed17..524157d5ca0 100644
--- a/jstests/replsets/db_reads_while_recovering_all_commands.js
+++ b/jstests/replsets/db_reads_while_recovering_all_commands.js
@@ -99,6 +99,7 @@ const allCommands = {
_shardsvrGetStatsForBalancing: {skip: isPrimaryOnly},
_shardsvrInsertGlobalIndexKey: {skip: isPrimaryOnly},
_shardsvrDeleteGlobalIndexKey: {skip: isPrimaryOnly},
+ _shardsvrWriteGlobalIndexKeys: {skip: isPrimaryOnly},
_shardsvrJoinMigrations: {skip: isAnInternalCommand},
_shardsvrMovePrimary: {skip: isPrimaryOnly},
_shardsvrMoveRange: {skip: isPrimaryOnly},
diff --git a/jstests/replsets/shardsvr_global_index_ddl_rollback.js b/jstests/replsets/global_index_ddl_rollback.js
index 5243f6d4d3d..5243f6d4d3d 100644
--- a/jstests/replsets/shardsvr_global_index_ddl_rollback.js
+++ b/jstests/replsets/global_index_ddl_rollback.js
diff --git a/jstests/replsets/shardsvr_global_index_crud_rollback.js b/jstests/replsets/shardsvr_global_index_crud_rollback.js
index 43c6500a4e0..7e4349c8945 100644
--- a/jstests/replsets/shardsvr_global_index_crud_rollback.js
+++ b/jstests/replsets/shardsvr_global_index_crud_rollback.js
@@ -81,6 +81,7 @@ rollbackTest.getTestFixture().nodes.forEach(function(node) {
// Current test implementation makes fastcount valid due to rolling back both a delete and an
// insert. After fixing fast count, we should make this test fail if fast count is not working
// properly.
+// TODO (SERVER-69847): add a rollback test for _shardsvrWriteGlobalIndexKeys too.
rollbackTest.stop();
})();
diff --git a/jstests/sharding/read_write_concern_defaults_application.js b/jstests/sharding/read_write_concern_defaults_application.js
index 676fb46567e..d36c36da6cc 100644
--- a/jstests/sharding/read_write_concern_defaults_application.js
+++ b/jstests/sharding/read_write_concern_defaults_application.js
@@ -157,6 +157,7 @@ let testCases = {
_shardsvrGetStatsForBalancing: {skip: "internal command"},
_shardsvrInsertGlobalIndexKey: {skip: "internal command"},
_shardsvrDeleteGlobalIndexKey: {skip: "internal command"},
+ _shardsvrWriteGlobalIndexKeys: {skip: "internal command"},
_shardsvrJoinMigrations: {skip: "internal command"},
_shardsvrMovePrimary: {skip: "internal command"},
_shardsvrMoveRange: {
diff --git a/src/mongo/db/global_index.cpp b/src/mongo/db/global_index.cpp
index cbe1d527d61..13852a11eda 100644
--- a/src/mongo/db/global_index.cpp
+++ b/src/mongo/db/global_index.cpp
@@ -63,7 +63,7 @@ BSONObj buildIndexEntry(const BSONObj& key, const BSONObj& docKey) {
// - No support for multikey indexes.
KeyString::Builder ks(KeyString::Version::V1);
- ks.resetToKey(key, KeyString::ALL_ASCENDING);
+ ks.resetToKey(BSONObj::stripFieldNames(key), KeyString::ALL_ASCENDING);
const auto& indexTB = ks.getTypeBits();
// Build the index entry, consisting of:
diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript
index 01b60cf14eb..be47f069997 100644
--- a/src/mongo/db/s/SConscript
+++ b/src/mongo/db/s/SConscript
@@ -488,6 +488,7 @@ env.Library(
'shardsvr_get_stats_for_balancing_command.cpp',
'shardsvr_insert_global_index_key_command.cpp',
'shardsvr_delete_global_index_key_command.cpp',
+ 'shardsvr_write_global_index_keys_command.cpp',
'shardsvr_join_migrations_command.cpp',
'shardsvr_merge_chunks_command.cpp',
'shardsvr_move_primary_command.cpp',
diff --git a/src/mongo/db/s/global_index/global_index_cloning_service_test.cpp b/src/mongo/db/s/global_index/global_index_cloning_service_test.cpp
index 98c6adeacd5..950584faa94 100644
--- a/src/mongo/db/s/global_index/global_index_cloning_service_test.cpp
+++ b/src/mongo/db/s/global_index/global_index_cloning_service_test.cpp
@@ -318,7 +318,6 @@ private:
const std::string _indexName{"global_x_1"};
const StringData _indexKey{"x"};
const BSONObj _indexSpec{BSON("key" << BSON(_indexKey << 1) << "unique" << true)};
- const RAIIServerParameterControllerForTest _enableFeature{"featureFlagGlobalIndexes", true};
ReadWriteConcernDefaultsLookupMock _lookupMock;
std::shared_ptr<StateTransitionController> _stateTransitionController;
@@ -327,6 +326,15 @@ private:
MockGlobalIndexClonerFetcher _fetcherCopyForVerification;
};
+MONGO_INITIALIZER_GENERAL(EnableFeatureFlagGlobalIndexes,
+ ("EndServerParameterRegistration"),
+ ("default"))
+(InitializerContext*) {
+ auto* param = ServerParameterSet::getNodeParameterSet()->get("featureFlagGlobalIndexes");
+ uassertStatusOK(
+ param->set(BSON("featureFlagGlobalIndexes" << true).firstElement(), boost::none));
+}
+
TEST_F(GlobalIndexClonerServiceTest, CloneInsertsToGlobalIndexCollection) {
auto doc = makeStateDocument();
auto opCtx = makeOperationContext();
diff --git a/src/mongo/db/s/global_index/global_index_inserter.cpp b/src/mongo/db/s/global_index/global_index_inserter.cpp
index 8db830be1ad..b2fb8163adc 100644
--- a/src/mongo/db/s/global_index/global_index_inserter.cpp
+++ b/src/mongo/db/s/global_index/global_index_inserter.cpp
@@ -91,8 +91,8 @@ void GlobalIndexInserter::processDoc(OperationContext* opCtx,
InsertGlobalIndexKey globalIndexEntryInsert(_indexUUID);
// Note: dbName is unused by command but required by idl.
globalIndexEntryInsert.setDbName({boost::none, "admin"});
- globalIndexEntryInsert.setDocKey(documentKey);
- globalIndexEntryInsert.setKey(indexKeyValues);
+ globalIndexEntryInsert.setGlobalIndexKeyEntry(
+ GlobalIndexKeyEntry(indexKeyValues, documentKey));
return txnClient.runCommand(_nss.db(), globalIndexEntryInsert.toBSON({}))
.thenRunOn(txnExec)
diff --git a/src/mongo/db/s/global_index_crud_commands.idl b/src/mongo/db/s/global_index_crud_commands.idl
index c21c99dbd12..2bf6889b63e 100644
--- a/src/mongo/db/s/global_index_crud_commands.idl
+++ b/src/mongo/db/s/global_index_crud_commands.idl
@@ -35,6 +35,18 @@ global:
imports:
- "mongo/db/basic_types.idl"
+structs:
+ GlobalIndexKeyEntry:
+ description: "Defines the global index key to be inserted or deleted."
+ strict: false
+ fields:
+ key:
+ type: object
+ description: "The index key."
+ docKey:
+ type: object
+ description: "The document key: the document's shard key followed by its _id."
+
commands:
_shardsvrInsertGlobalIndexKey:
description: "Internal command to insert a key into a global index container."
@@ -45,13 +57,9 @@ commands:
namespace: type
api_version: ""
reply_type: OkReply
- fields:
- key:
- type: object
- description: "The index key."
- docKey:
- type: object
- description: "The document key: the document's shard key followed by its _id."
+ inline_chained_structs: true
+ chained_structs:
+ GlobalIndexKeyEntry: GlobalIndexKeyEntry
_shardsvrDeleteGlobalIndexKey:
description: "Internal command to delete a key from a global index container."
@@ -62,10 +70,19 @@ commands:
namespace: type
api_version: ""
reply_type: OkReply
+ inline_chained_structs: true
+ chained_structs:
+ GlobalIndexKeyEntry: GlobalIndexKeyEntry
+
+ _shardsvrWriteGlobalIndexKeys:
+ description: "Internal command to run _shardsvrInsertGlobalIndexKey and _shardsvrDeleteGlobalIndexKey statements in bulk."
+ command_name: _shardsvrWriteGlobalIndexKeys
+ cpp_name: WriteGlobalIndexKeys
+ namespace: ignored
+ api_version: ""
+ reply_type: OkReply
fields:
- key:
- type: object
- description: "The index key."
- docKey:
- type: object
- description: "The document key: the document's shard key followed by its _id."
+ ops:
+ type: array<object>
+ cpp_name: ops
+ description: "Array of _shardsvrInsertGlobalIndexKey and _shardsvrDeleteGlobalIndexKey commands"
diff --git a/src/mongo/db/s/shardsvr_create_global_index_command.cpp b/src/mongo/db/s/shardsvr_create_global_index_command.cpp
index c955676a169..cdf86c95e60 100644
--- a/src/mongo/db/s/shardsvr_create_global_index_command.cpp
+++ b/src/mongo/db/s/shardsvr_create_global_index_command.cpp
@@ -84,11 +84,6 @@ public:
}
void typedRun(OperationContext* opCtx) {
-
- uassert(ErrorCodes::CommandNotSupported,
- "_shardsvrCreateGlobalIndex command not enabled",
- gFeatureFlagGlobalIndexes.isEnabledAndIgnoreFCV());
-
const auto indexUUID = request().getCommandParameter();
global_index::createContainer(opCtx, indexUUID);
}
@@ -102,7 +97,10 @@ public:
ActionType::internal));
}
};
-} shardsvrCreateGlobalIndexCommand;
+};
+
+MONGO_REGISTER_FEATURE_FLAGGED_COMMAND(ShardsvrCreateGlobalIndexCommand,
+ mongo::gFeatureFlagGlobalIndexes);
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/s/shardsvr_delete_global_index_key_command.cpp b/src/mongo/db/s/shardsvr_delete_global_index_key_command.cpp
index 36445981710..f7e384724ee 100644
--- a/src/mongo/db/s/shardsvr_delete_global_index_key_command.cpp
+++ b/src/mongo/db/s/shardsvr_delete_global_index_key_command.cpp
@@ -56,11 +56,6 @@ public:
using InvocationBase::InvocationBase;
void typedRun(OperationContext* opCtx) {
-
- uassert(ErrorCodes::CommandNotSupported,
- "_shardsvrDeleteGlobalIndexKey command not enabled",
- gFeatureFlagGlobalIndexes.isEnabledAndIgnoreFCV());
-
uassert(6924200,
"_shardsvrDeleteGlobalIndexKey must run inside a multi-doc transaction.",
opCtx->inMultiDocumentTransaction());
@@ -94,8 +89,10 @@ public:
BasicCommand::AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
return BasicCommand::AllowedOnSecondary::kNever;
}
+};
-} shardsvrDeleteGlobalIndexKeyCmd;
+MONGO_REGISTER_FEATURE_FLAGGED_COMMAND(ShardsvrDeleteGlobalIndexKeyCmd,
+ mongo::gFeatureFlagGlobalIndexes);
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/s/shardsvr_drop_global_index_command.cpp b/src/mongo/db/s/shardsvr_drop_global_index_command.cpp
index 69e17b2c2d1..8b3dff6fcd9 100644
--- a/src/mongo/db/s/shardsvr_drop_global_index_command.cpp
+++ b/src/mongo/db/s/shardsvr_drop_global_index_command.cpp
@@ -41,8 +41,7 @@
namespace mongo {
namespace {
-class ShardsvrCreateGlobalIndexCommand final
- : public TypedCommand<ShardsvrCreateGlobalIndexCommand> {
+class ShardsvrDropGlobalIndexCommand final : public TypedCommand<ShardsvrDropGlobalIndexCommand> {
public:
using Request = DropGlobalIndex;
@@ -84,11 +83,6 @@ public:
}
void typedRun(OperationContext* opCtx) {
-
- uassert(ErrorCodes::CommandNotSupported,
- "_shardsvrDropGlobalIndex command not enabled",
- gFeatureFlagGlobalIndexes.isEnabledAndIgnoreFCV());
-
const auto indexUUID = request().getCommandParameter();
global_index::dropContainer(opCtx, indexUUID);
}
@@ -102,7 +96,10 @@ public:
ActionType::internal));
}
};
-} shardsvrDropGlobalIndexCommand;
+};
+
+MONGO_REGISTER_FEATURE_FLAGGED_COMMAND(ShardsvrDropGlobalIndexCommand,
+ mongo::gFeatureFlagGlobalIndexes);
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/s/shardsvr_insert_global_index_key_command.cpp b/src/mongo/db/s/shardsvr_insert_global_index_key_command.cpp
index 01f1ce3c6a1..6efc577d432 100644
--- a/src/mongo/db/s/shardsvr_insert_global_index_key_command.cpp
+++ b/src/mongo/db/s/shardsvr_insert_global_index_key_command.cpp
@@ -56,11 +56,6 @@ public:
using InvocationBase::InvocationBase;
void typedRun(OperationContext* opCtx) {
-
- uassert(ErrorCodes::CommandNotSupported,
- "_shardsvrInsertGlobalIndexKey command not enabled",
- gFeatureFlagGlobalIndexes.isEnabledAndIgnoreFCV());
-
uassert(6789400,
"_shardsvrInsertGlobalIndexKey must run inside a multi-doc transaction.",
opCtx->inMultiDocumentTransaction());
@@ -94,8 +89,10 @@ public:
BasicCommand::AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
return BasicCommand::AllowedOnSecondary::kNever;
}
+};
-} shardsvrInsertGlobalIndexKeyCmd;
+MONGO_REGISTER_FEATURE_FLAGGED_COMMAND(ShardsvrInsertGlobalIndexKeyCmd,
+ mongo::gFeatureFlagGlobalIndexes);
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/s/shardsvr_write_global_index_keys_command.cpp b/src/mongo/db/s/shardsvr_write_global_index_keys_command.cpp
new file mode 100644
index 00000000000..fc9d7199f0a
--- /dev/null
+++ b/src/mongo/db/s/shardsvr_write_global_index_keys_command.cpp
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2022-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.
+ */
+
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kIndex
+
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/global_index.h"
+#include "mongo/db/s/global_index_crud_commands_gen.h"
+#include "mongo/db/server_feature_flags_gen.h"
+
+namespace mongo {
+namespace {
+
+class ShardsvrWriteGlobalIndexKeysCmd final : public TypedCommand<ShardsvrWriteGlobalIndexKeysCmd> {
+public:
+ using Request = WriteGlobalIndexKeys;
+
+ bool allowedInTransactions() const final {
+ return true;
+ }
+
+ class Invocation final : public InvocationBase {
+
+ public:
+ using InvocationBase::InvocationBase;
+
+ void typedRun(OperationContext* opCtx);
+
+ private:
+ void doCheckAuthorization(OperationContext* opCtx) const override {
+ uassert(ErrorCodes::Unauthorized,
+ "Unauthorized",
+ AuthorizationSession::get(opCtx->getClient())
+ ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
+ ActionType::internal));
+ }
+
+ bool supportsWriteConcern() const override {
+ return false;
+ }
+
+ NamespaceString ns() const override {
+ return {request().getDbName(), ""};
+ }
+ };
+
+ std::string help() const override {
+ return "Internal command to perform multiple global index key writes in bulk.";
+ }
+
+ BasicCommand::AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
+ return BasicCommand::AllowedOnSecondary::kNever;
+ }
+};
+
+void ShardsvrWriteGlobalIndexKeysCmd::Invocation::typedRun(OperationContext* opCtx) {
+ uassert(6789500,
+ "_shardsvrWriteGlobalIndexKeys must run inside a multi-doc transaction.",
+ opCtx->inMultiDocumentTransaction());
+
+ const auto& ops = request().getOps();
+
+ uassert(6789502, "_shardsvrWriteGlobalIndexKeys 'ops' field cannot be empty", ops.size());
+
+ for (const auto& op : ops) {
+ const auto cmd = op.firstElementFieldNameStringData();
+ uassert(6789501,
+ str::stream() << "_shardsvrWriteGlobalIndexKeys ops entries must be of type "
+ << InsertGlobalIndexKey::kCommandParameterFieldName << " or "
+ << DeleteGlobalIndexKey::kCommandParameterFieldName,
+ cmd == InsertGlobalIndexKey::kCommandParameterFieldName ||
+ cmd == DeleteGlobalIndexKey::kCommandParameterFieldName);
+
+ const auto uuid = uassertStatusOK(UUID::parse(op[cmd]));
+ const auto key = GlobalIndexKeyEntry::parse(IDLParserContext{"GlobalIndexKeyEntry"}, op);
+ if (cmd == InsertGlobalIndexKey::kCommandParameterFieldName) {
+ global_index::insertKey(opCtx, uuid, key.getKey(), key.getDocKey());
+ } else {
+ global_index::deleteKey(opCtx, uuid, key.getKey(), key.getDocKey());
+ }
+ }
+}
+
+MONGO_REGISTER_FEATURE_FLAGGED_COMMAND(ShardsvrWriteGlobalIndexKeysCmd,
+ mongo::gFeatureFlagGlobalIndexes);
+
+} // namespace
+} // namespace mongo