From 2115bfcb0ae08ca1dfb1cbd6cfc95974b4f11240 Mon Sep 17 00:00:00 2001 From: Josef Ahmad Date: Tue, 27 Sep 2022 12:04:59 +0000 Subject: SERVER-67895 Add _shardsvrWriteGlobalIndexKeys command --- jstests/auth/lib/commands_lib.js | 21 + jstests/core/views/views_all_commands.js | 1 + .../shardsvr_global_index_crud_bulk.js | 649 +++++++++++++++++++++ .../all_commands_downgrading_to_upgraded.js | 1 + .../db_reads_while_recovering_all_commands.js | 1 + jstests/replsets/global_index_ddl_rollback.js | 56 ++ .../shardsvr_global_index_crud_rollback.js | 1 + .../replsets/shardsvr_global_index_ddl_rollback.js | 56 -- .../read_write_concern_defaults_application.js | 1 + src/mongo/db/global_index.cpp | 2 +- src/mongo/db/s/SConscript | 1 + .../global_index_cloning_service_test.cpp | 10 +- .../db/s/global_index/global_index_inserter.cpp | 4 +- src/mongo/db/s/global_index_crud_commands.idl | 43 +- .../db/s/shardsvr_create_global_index_command.cpp | 10 +- .../s/shardsvr_delete_global_index_key_command.cpp | 9 +- .../db/s/shardsvr_drop_global_index_command.cpp | 13 +- .../s/shardsvr_insert_global_index_key_command.cpp | 9 +- .../s/shardsvr_write_global_index_keys_command.cpp | 115 ++++ 19 files changed, 904 insertions(+), 99 deletions(-) create mode 100644 jstests/noPassthrough/shardsvr_global_index_crud_bulk.js create mode 100644 jstests/replsets/global_index_ddl_rollback.js delete mode 100644 jstests/replsets/shardsvr_global_index_ddl_rollback.js create mode 100644 src/mongo/db/s/shardsvr_write_global_index_keys_command.cpp 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 @@ -2838,6 +2838,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}, 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/global_index_ddl_rollback.js b/jstests/replsets/global_index_ddl_rollback.js new file mode 100644 index 00000000000..5243f6d4d3d --- /dev/null +++ b/jstests/replsets/global_index_ddl_rollback.js @@ -0,0 +1,56 @@ +/** + * Tests that global index container ddl operations can be rolled back. + * + * @tags: [ + * featureFlagGlobalIndexes, + * requires_fcv_62, + * requires_replication, + * ] + */ +(function() { +'use strict'; + +load('jstests/replsets/libs/rollback_test.js'); + +function uuidToNss(uuid) { + const [_, uuidString] = uuid.toString().match(/"((?:\\.|[^"\\])*)"/); + return "globalIndexes." + uuidString; +} + +const rollbackTest = new RollbackTest(jsTestName()); + +const primary = rollbackTest.getPrimary(); +const adminDB = primary.getDB("admin"); +const globalIndexCreateUUID = UUID(); +const globalIndexDropUUID = UUID(); + +// Create a global index container to be dropped. +assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexDropUUID})); + +rollbackTest.transitionToRollbackOperations(); + +// Create a global index container to be rolled back. +assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexCreateUUID})); +// Drop a global index container, operation should be rolled back. +assert.commandWorked(adminDB.runCommand({_shardsvrDropGlobalIndex: globalIndexDropUUID})); + +// Perform the rollback. +rollbackTest.transitionToSyncSourceOperationsBeforeRollback(); +rollbackTest.transitionToSyncSourceOperationsDuringRollback(); +rollbackTest.transitionToSteadyStateOperations(); + +rollbackTest.getTestFixture().nodes.forEach(function(node) { + const nodeDB = node.getDB("system"); + + // Check globalIndexCreateUUID creation is rolled back and does not exist. + var res = + nodeDB.runCommand({listCollections: 1, filter: {name: uuidToNss(globalIndexCreateUUID)}}); + assert.eq(res.cursor.firstBatch.length, 0); + + // Check globalIndexDropUUID drop is rolled back and still exists. + res = nodeDB.runCommand({listCollections: 1, filter: {name: uuidToNss(globalIndexDropUUID)}}); + assert.eq(res.cursor.firstBatch.length, 1); +}); + +rollbackTest.stop(); +})(); 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/replsets/shardsvr_global_index_ddl_rollback.js b/jstests/replsets/shardsvr_global_index_ddl_rollback.js deleted file mode 100644 index 5243f6d4d3d..00000000000 --- a/jstests/replsets/shardsvr_global_index_ddl_rollback.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Tests that global index container ddl operations can be rolled back. - * - * @tags: [ - * featureFlagGlobalIndexes, - * requires_fcv_62, - * requires_replication, - * ] - */ -(function() { -'use strict'; - -load('jstests/replsets/libs/rollback_test.js'); - -function uuidToNss(uuid) { - const [_, uuidString] = uuid.toString().match(/"((?:\\.|[^"\\])*)"/); - return "globalIndexes." + uuidString; -} - -const rollbackTest = new RollbackTest(jsTestName()); - -const primary = rollbackTest.getPrimary(); -const adminDB = primary.getDB("admin"); -const globalIndexCreateUUID = UUID(); -const globalIndexDropUUID = UUID(); - -// Create a global index container to be dropped. -assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexDropUUID})); - -rollbackTest.transitionToRollbackOperations(); - -// Create a global index container to be rolled back. -assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexCreateUUID})); -// Drop a global index container, operation should be rolled back. -assert.commandWorked(adminDB.runCommand({_shardsvrDropGlobalIndex: globalIndexDropUUID})); - -// Perform the rollback. -rollbackTest.transitionToSyncSourceOperationsBeforeRollback(); -rollbackTest.transitionToSyncSourceOperationsDuringRollback(); -rollbackTest.transitionToSteadyStateOperations(); - -rollbackTest.getTestFixture().nodes.forEach(function(node) { - const nodeDB = node.getDB("system"); - - // Check globalIndexCreateUUID creation is rolled back and does not exist. - var res = - nodeDB.runCommand({listCollections: 1, filter: {name: uuidToNss(globalIndexCreateUUID)}}); - assert.eq(res.cursor.firstBatch.length, 0); - - // Check globalIndexDropUUID drop is rolled back and still exists. - res = nodeDB.runCommand({listCollections: 1, filter: {name: uuidToNss(globalIndexDropUUID)}}); - assert.eq(res.cursor.firstBatch.length, 1); -}); - -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; @@ -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 + 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 { +class ShardsvrDropGlobalIndexCommand final : public TypedCommand { 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 + * . + * + * 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 { +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 -- cgit v1.2.1