diff options
author | Kaitlin Mahar <kaitlin.mahar@mongodb.com> | 2023-04-19 22:24:35 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-04-20 00:39:50 +0000 |
commit | 032c7f7d75006a6530639a80842afdd3ac13e5f6 (patch) | |
tree | c2fe8b4e02162045860ab36b5666c0d8d41caebb | |
parent | f5b992c420e971bee2b951e3f0e7d947a3b68f2e (diff) | |
download | mongo-032c7f7d75006a6530639a80842afdd3ac13e5f6.tar.gz |
SERVER-74390 Add numErrors to bulkWrite response
-rw-r--r-- | jstests/core/write/bulk/bulk_write.js | 22 | ||||
-rw-r--r-- | jstests/core/write/bulk/bulk_write_delete_cursor.js | 7 | ||||
-rw-r--r-- | jstests/core/write/bulk/bulk_write_getMore.js | 1 | ||||
-rw-r--r-- | jstests/core/write/bulk/bulk_write_insert_cursor.js | 2 | ||||
-rw-r--r-- | jstests/core/write/bulk/bulk_write_non_retryable_cursor.js | 4 | ||||
-rw-r--r-- | jstests/core/write/bulk/bulk_write_non_transaction.js | 87 | ||||
-rw-r--r-- | jstests/core/write/bulk/bulk_write_update_cursor.js | 14 | ||||
-rw-r--r-- | src/mongo/db/bulk_write_shard_test.cpp | 48 | ||||
-rw-r--r-- | src/mongo/db/commands/bulk_write.cpp | 35 | ||||
-rw-r--r-- | src/mongo/db/commands/bulk_write.h | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/bulk_write.idl | 3 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_bulk_write_cmd.cpp | 12 |
12 files changed, 170 insertions, 67 deletions
diff --git a/jstests/core/write/bulk/bulk_write.js b/jstests/core/write/bulk/bulk_write.js index ff6ed1eec1e..f1767bd0338 100644 --- a/jstests/core/write/bulk/bulk_write.js +++ b/jstests/core/write/bulk/bulk_write.js @@ -39,17 +39,21 @@ var res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert.eq(coll.find().itcount(), 1); assert.eq(coll1.find().itcount(), 0); coll.drop(); // Make sure ops and nsInfo can take arrays properly -assert.commandWorked(db.adminCommand({ +res = db.adminCommand({ bulkWrite: 1, ops: [{insert: 1, document: {skey: "MongoDB"}}, {insert: 0, document: {skey: "MongoDB"}}], nsInfo: [{ns: "test.coll"}, {ns: "test.coll1"}] -})); +}); + +assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert.eq(coll.find().itcount(), 1); assert.eq(coll1.find().itcount(), 1); @@ -57,11 +61,14 @@ coll.drop(); coll1.drop(); // Test 2 inserts into the same namespace -assert.commandWorked(db.adminCommand({ +res = db.adminCommand({ bulkWrite: 1, ops: [{insert: 0, document: {skey: "MongoDB"}}, {insert: 0, document: {skey: "MongoDB"}}], nsInfo: [{ns: "test.coll"}] -})); +}); + +assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert.eq(coll.find().itcount(), 2); assert.eq(coll1.find().itcount(), 0); @@ -71,12 +78,15 @@ coll.drop(); assert.commandWorked(coll.insert({_id: 1})); assert.commandWorked(db.runCommand({collMod: "coll", validator: {a: {$exists: true}}})); -assert.commandWorked(db.adminCommand({ +res = db.adminCommand({ bulkWrite: 1, ops: [{insert: 0, document: {_id: 3, skey: "MongoDB"}}], nsInfo: [{ns: "test.coll"}], bypassDocumentValidation: true, -})); +}); + +assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert.eq(1, coll.count({_id: 3})); diff --git a/jstests/core/write/bulk/bulk_write_delete_cursor.js b/jstests/core/write/bulk/bulk_write_delete_cursor.js index 4e515cef170..360d18c7841 100644 --- a/jstests/core/write/bulk/bulk_write_delete_cursor.js +++ b/jstests/core/write/bulk/bulk_write_delete_cursor.js @@ -38,6 +38,7 @@ var res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -59,6 +60,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -81,6 +83,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -103,6 +106,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -125,6 +129,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); assert.docEq(res.cursor.firstBatch[0].value, {_id: 1, skey: "MongoDB"}); @@ -146,6 +151,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 0}); assert(!res.cursor.firstBatch[0].value); @@ -174,6 +180,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); diff --git a/jstests/core/write/bulk/bulk_write_getMore.js b/jstests/core/write/bulk/bulk_write_getMore.js index b9e6e2f092a..397e2847efa 100644 --- a/jstests/core/write/bulk/bulk_write_getMore.js +++ b/jstests/core/write/bulk/bulk_write_getMore.js @@ -49,6 +49,7 @@ var res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert(res.cursor.id != 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); diff --git a/jstests/core/write/bulk/bulk_write_insert_cursor.js b/jstests/core/write/bulk/bulk_write_insert_cursor.js index 9391fc1ffc5..a4168827bb0 100644 --- a/jstests/core/write/bulk/bulk_write_insert_cursor.js +++ b/jstests/core/write/bulk/bulk_write_insert_cursor.js @@ -32,6 +32,7 @@ var res = db.adminCommand( {bulkWrite: 1, ops: [{insert: 0, document: {skey: "MongoDB"}}], nsInfo: [{ns: "test.coll"}]}); assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert(res.cursor.id == 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); @@ -50,6 +51,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); assert(res.cursor.id == 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); diff --git a/jstests/core/write/bulk/bulk_write_non_retryable_cursor.js b/jstests/core/write/bulk/bulk_write_non_retryable_cursor.js index aca7cfd83d5..89e066a59d7 100644 --- a/jstests/core/write/bulk/bulk_write_non_retryable_cursor.js +++ b/jstests/core/write/bulk/bulk_write_non_retryable_cursor.js @@ -47,6 +47,7 @@ var res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -73,6 +74,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 1}); @@ -97,6 +99,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -119,6 +122,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); diff --git a/jstests/core/write/bulk/bulk_write_non_transaction.js b/jstests/core/write/bulk/bulk_write_non_transaction.js index 24e83301ef3..967cab5da61 100644 --- a/jstests/core/write/bulk/bulk_write_non_transaction.js +++ b/jstests/core/write/bulk/bulk_write_non_transaction.js @@ -114,38 +114,6 @@ assert.commandFailedWithCode( assert.eq(coll.find().itcount(), 0); assert.eq(coll1.find().itcount(), 0); -// Test that a write can fail part way through a write and the write partially executes. -assert.commandWorked(db.adminCommand({ - bulkWrite: 1, - ops: [ - {insert: 0, document: {_id: 1, skey: "MongoDB"}}, - {insert: 0, document: {_id: 1, skey: "MongoDB"}}, - {insert: 1, document: {skey: "MongoDB"}} - ], - nsInfo: [{ns: "test.coll"}, {ns: "test.coll1"}] -})); - -assert.eq(coll.find().itcount(), 1); -assert.eq(coll1.find().itcount(), 0); -coll.drop(); -coll1.drop(); - -assert.commandWorked(db.adminCommand({ - bulkWrite: 1, - ops: [ - {insert: 0, document: {_id: 1, skey: "MongoDB"}}, - {insert: 0, document: {_id: 1, skey: "MongoDB"}}, - {insert: 1, document: {skey: "MongoDB"}} - ], - nsInfo: [{ns: "test.coll"}, {ns: "test.coll1"}], - ordered: false -})); - -assert.eq(coll.find().itcount(), 1); -assert.eq(coll1.find().itcount(), 1); -coll.drop(); -coll1.drop(); - // Make sure update multi:true + return fails the op. var res = db.adminCommand({ bulkWrite: 1, @@ -162,6 +130,7 @@ var res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.InvalidOptions}); assert(!res.cursor.firstBatch[1]); @@ -182,6 +151,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 0, idx: 1, code: ErrorCodes.InvalidOptions}); @@ -203,6 +173,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.InvalidNamespace}); assert(!res.cursor.firstBatch[1]); @@ -230,6 +201,9 @@ res = db.adminCommand({ ordered: false }); +assert.commandWorked(res); +assert.eq(res.numErrors, 1); + cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.DuplicateKey}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 0}); assert.docEq(res.cursor.firstBatch[1].upserted, {index: 0, _id: 1}); @@ -258,6 +232,9 @@ res = db.adminCommand({ nsInfo: [{ns: "test.coll2"}, {ns: "test.coll"}], }); +assert.commandWorked(res); +assert.eq(res.numErrors, 1); + cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.DuplicateKey}); assert(!res.cursor.firstBatch[1]); coll.drop(); @@ -288,6 +265,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); assert(res.cursor.id == 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); @@ -318,6 +296,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); assert(res.cursor.id == 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); @@ -346,6 +325,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); assert(res.cursor.id == 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); @@ -373,6 +353,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); assert(res.cursor.id == 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, n: 1, idx: 0}); @@ -395,6 +376,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.InvalidOptions}); assert(!res.cursor.firstBatch[1]); @@ -410,6 +392,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 0, idx: 1, code: ErrorCodes.InvalidOptions}); @@ -430,6 +413,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 1); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.InvalidNamespace}); assert(!res.cursor.firstBatch[1]); @@ -449,6 +433,9 @@ res = db.adminCommand({ ordered: false }); +assert.commandWorked(res); +assert.eq(res.numErrors, 1); + cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.InvalidNamespace}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); assert.docEq(res.cursor.firstBatch[1].value, {_id: 1, skey: "MongoDB"}); @@ -473,6 +460,9 @@ res = db.adminCommand({ nsInfo: [{ns: "test.system.profile"}, {ns: "test.coll"}], }); +assert.commandWorked(res); +assert.eq(res.numErrors, 1); + cursorEntryValidator(res.cursor.firstBatch[0], {ok: 0, idx: 0, code: ErrorCodes.InvalidNamespace}); assert(!res.cursor.firstBatch[1]); @@ -501,6 +491,7 @@ res = db.adminCommand({ let processCursor = true; try { assert.commandWorked(res); + assert.eq(res.numErrors, 0); } catch { processCursor = false; assert.commandFailedWithCode(res, [ErrorCodes.BadValue]); @@ -533,6 +524,7 @@ res = db.adminCommand({ processCursor = true; try { assert.commandWorked(res); + assert.eq(res.numErrors, 0); } catch { processCursor = false; assert.commandFailedWithCode(res, [ErrorCodes.BadValue]); @@ -555,12 +547,39 @@ coll.drop(); assert.commandWorked(coll.insert({_id: 1})); assert.commandWorked(db.runCommand({collMod: "coll", validator: {a: {$exists: true}}})); -assert.commandWorked(db.adminCommand({ +res = db.adminCommand({ bulkWrite: 1, ops: [{insert: 0, document: {_id: 3, skey: "MongoDB"}}], nsInfo: [{ns: "test.coll"}], bypassDocumentValidation: false, -})); +}); +assert.commandWorked(res); +assert.eq(res.numErrors, 1); assert.eq(0, coll.count({_id: 3})); +coll.drop(); + +// Test that we correctly count multiple errors for different write types when ordered=false. +res = db.adminCommand({ + bulkWrite: 1, + ops: [ + {insert: 0, document: {_id: 1}}, + {insert: 0, document: {_id: 2}}, + // error 1: duplicate key error + {insert: 0, document: {_id: 1}}, + {delete: 0, filter: {_id: 2}}, + // error 2: user can't write to namespace + {delete: 1, filter: {_id: 0}}, + {update: 0, filter: {_id: 0}, updateMods: {$set: {x: 1}}}, + // error 3: invalid update operator + {update: 0, filter: {_id: 0}, updateMods: {$blah: {x: 1}}}, + ], + nsInfo: [{ns: "test.coll"}, {ns: "test.system.profile"}], + ordered: false +}); + +assert.commandWorked(res); +assert.eq(res.numErrors, 3); + +coll.drop(); })(); diff --git a/jstests/core/write/bulk/bulk_write_update_cursor.js b/jstests/core/write/bulk/bulk_write_update_cursor.js index 03f11014e23..bf4148f4fae 100644 --- a/jstests/core/write/bulk/bulk_write_update_cursor.js +++ b/jstests/core/write/bulk/bulk_write_update_cursor.js @@ -39,6 +39,7 @@ var res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 1}); @@ -61,6 +62,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 1}); @@ -83,6 +85,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 1}); @@ -111,6 +114,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -139,6 +143,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -161,6 +166,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, nModified: 1}); assert.docEq(res.cursor.firstBatch[0].value, {_id: 1, skey: "MongoDB2"}); @@ -182,6 +188,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, nModified: 0}); assert(!res.cursor.firstBatch[0].value); @@ -208,6 +215,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, nModified: 0}); assert.docEq(res.cursor.firstBatch[0].upserted, {index: 0, _id: 1}); @@ -234,6 +242,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, nModified: 0}); assert.docEq(res.cursor.firstBatch[0].upserted, {index: 0, _id: 1}); @@ -255,6 +264,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 1}); @@ -282,6 +292,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, nModified: 1}); assert.eq(res.cursor.firstBatch[0].nModified, 1); @@ -309,6 +320,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, n: 1}); @@ -335,6 +347,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, n: 1}); cursorEntryValidator(res.cursor.firstBatch[1], {ok: 1, idx: 1, nModified: 1}); @@ -362,6 +375,7 @@ res = db.adminCommand({ }); assert.commandWorked(res); +assert.eq(res.numErrors, 0); cursorEntryValidator(res.cursor.firstBatch[0], {ok: 1, idx: 0, nModified: 0}); assert.docEq(res.cursor.firstBatch[0].upserted, {index: 0, _id: 1}); diff --git a/src/mongo/db/bulk_write_shard_test.cpp b/src/mongo/db/bulk_write_shard_test.cpp index 77320fd28e9..ce4aef8d85b 100644 --- a/src/mongo/db/bulk_write_shard_test.cpp +++ b/src/mongo/db/bulk_write_shard_test.cpp @@ -258,12 +258,14 @@ TEST_F(BulkWriteShardTest, ThreeSuccessfulInsertsOrdered) { nssShardedCollection2, dbVersionTestDb2, shardVersionShardedCollection2), }); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(3, replyItems.size()); for (const auto& reply : replyItems) { ASSERT_OK(reply.getStatus()); } + ASSERT_EQ(0, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -278,10 +280,12 @@ TEST_F(BulkWriteShardTest, OneFailingShardedOneSkippedUnshardedSuccessInsertOrde nsInfoWithShardDatabaseVersions( nssUnshardedCollection1, dbVersionTestDb1, ShardVersion::UNSHARDED())}); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(1, replyItems.size()); ASSERT_EQ(ErrorCodes::StaleConfig, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -296,10 +300,12 @@ TEST_F(BulkWriteShardTest, TwoFailingShardedInsertsOrdered) { nssShardedCollection1, dbVersionTestDb1, incorrectShardVersion), }); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(1, replyItems.size()); ASSERT_EQ(ErrorCodes::StaleConfig, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -314,11 +320,13 @@ TEST_F(BulkWriteShardTest, OneSuccessfulShardedOneFailingShardedOrdered) { nsInfoWithShardDatabaseVersions( nssShardedCollection2, dbVersionTestDb2, incorrectShardVersion)}); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(2, replyItems.size()); ASSERT_OK(replyItems.front().getStatus()); ASSERT_EQ(ErrorCodes::StaleConfig, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -332,10 +340,12 @@ TEST_F(BulkWriteShardTest, OneFailingShardedOneSkippedShardedUnordered) { nssShardedCollection1, dbVersionTestDb1, incorrectShardVersion)}); request.setOrdered(false); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(1, replyItems.size()); ASSERT_EQ(ErrorCodes::StaleConfig, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -351,10 +361,12 @@ TEST_F(BulkWriteShardTest, OneSuccessfulShardedOneFailingShardedUnordered) { nssShardedCollection2, dbVersionTestDb2, shardVersionShardedCollection2)}); request.setOrdered(false); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(1, replyItems.size()); ASSERT_EQ(ErrorCodes::StaleConfig, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -373,12 +385,14 @@ TEST_F(BulkWriteShardTest, InsertsAndUpdatesSuccessOrdered) { nsInfoWithShardDatabaseVersions( nssUnshardedCollection1, dbVersionTestDb1, ShardVersion::UNSHARDED())}); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(4, replyItems.size()); for (const auto& reply : replyItems) { ASSERT_OK(reply.getStatus()); } + ASSERT_EQ(0, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -399,12 +413,14 @@ TEST_F(BulkWriteShardTest, InsertsAndUpdatesSuccessUnordered) { request.setOrdered(false); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(4, replyItems.size()); for (const auto& reply : replyItems) { ASSERT_OK(reply.getStatus()); } + ASSERT_EQ(0, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -425,11 +441,13 @@ TEST_F(BulkWriteShardTest, InsertsAndUpdatesFailUnordered) { request.setOrdered(false); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(2, replyItems.size()); ASSERT_OK(replyItems.front().getStatus()); ASSERT_EQ(ErrorCodes::StaleConfig, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -449,11 +467,13 @@ TEST_F(BulkWriteShardTest, InsertsAndUpdatesFailUnordered) { // request.setOrdered(false); -// const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); +// const auto& [replyItems, retriedStmtIds, numErrors] = +// bulk_write::performWrites(opCtx(), request); // ASSERT_EQ(2, replyItems.size()); // ASSERT_OK(replyItems.front().getStatus()); // ASSERT_EQ(ErrorCodes::StaleConfig, replyItems[1].getStatus().code()); +// ASSERT_EQ(1, numErrors); // OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); // } @@ -470,10 +490,12 @@ TEST_F(BulkWriteShardTest, FirstFailsRestSkippedStaleDbVersionOrdered) { nsInfoWithShardDatabaseVersions( nssShardedCollection2, dbVersionTestDb2, shardVersionShardedCollection2)}); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(1, replyItems.size()); ASSERT_EQ(ErrorCodes::StaleDbVersion, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } @@ -491,11 +513,13 @@ TEST_F(BulkWriteShardTest, FirstFailsRestSkippedStaleDbVersionUnordered) { nssShardedCollection2, dbVersionTestDb2, shardVersionShardedCollection2)}); request.setOrdered(false); - const auto& [replyItems, retriedStmtIds] = bulk_write::performWrites(opCtx(), request); + const auto& [replyItems, retriedStmtIds, numErrors] = + bulk_write::performWrites(opCtx(), request); ASSERT_EQ(2, replyItems.size()); ASSERT_OK(replyItems.front().getStatus()); ASSERT_EQ(ErrorCodes::StaleDbVersion, replyItems.back().getStatus().code()); + ASSERT_EQ(1, numErrors); OperationShardingState::get(opCtx()).resetShardingOperationFailedStatus(); } diff --git a/src/mongo/db/commands/bulk_write.cpp b/src/mongo/db/commands/bulk_write.cpp index 6a5ab7b7039..8c0ba323346 100644 --- a/src/mongo/db/commands/bulk_write.cpp +++ b/src/mongo/db/commands/bulk_write.cpp @@ -238,6 +238,7 @@ public: opCtx, writes.results[i].getStatus(), idx, 0 /* numErrors */)) { auto replyItem = BulkWriteReplyItem(idx, error.get().getStatus()); _replies.emplace_back(replyItem); + _numErrors++; } else { auto replyItem = BulkWriteReplyItem(idx); replyItem.setN(writes.results[i].getValue().getN()); @@ -288,6 +289,7 @@ public: void addErrorReply(size_t currentOpIdx, const Status& status) { _replies.emplace_back(currentOpIdx, status); + _numErrors++; } std::vector<BulkWriteReplyItem>& getReplies() { @@ -298,10 +300,16 @@ public: return _retriedStmtIds; } + int getNumErrors() { + return _numErrors; + } + private: const BulkWriteCommandRequest& _req; std::vector<BulkWriteReplyItem> _replies; std::vector<int32_t> _retriedStmtIds; + /// The number of error replies contained in _replies. + int _numErrors = 0; }; void finishCurOp(OperationContext* opCtx, CurOp* curOp) { @@ -813,9 +821,10 @@ public: bulk_write_common::validateRequest(req, opCtx->isRetryableWrite()); // Apply all of the write operations. - auto [replies, retriedStmtIds] = bulk_write::performWrites(opCtx, req); + auto [replies, retriedStmtIds, numErrors] = bulk_write::performWrites(opCtx, req); - return _populateCursorReply(opCtx, req, std::move(replies), std::move(retriedStmtIds)); + return _populateCursorReply( + opCtx, req, std::move(replies), std::move(retriedStmtIds), numErrors); } void doCheckAuthorization(OperationContext* opCtx) const final try { @@ -835,7 +844,8 @@ public: Reply _populateCursorReply(OperationContext* opCtx, const BulkWriteCommandRequest& req, bulk_write::BulkWriteReplyItems replies, - bulk_write::RetriedStmtIds retriedStmtIds) { + bulk_write::RetriedStmtIds retriedStmtIds, + int numErrors) { auto reqObj = unparsedRequest().body; const NamespaceString cursorNss = NamespaceString::makeBulkWriteNSS(); auto expCtx = make_intrusive<ExpressionContext>( @@ -893,8 +903,10 @@ public: if (exec->isEOF()) { invariant(numRepliesInFirstBatch == replies.size()); collectTelemetryMongod(opCtx, reqObj, numRepliesInFirstBatch); - auto reply = BulkWriteCommandReply(BulkWriteCommandResponseCursor( - 0, std::vector<BulkWriteReplyItem>(std::move(replies)))); + auto reply = BulkWriteCommandReply( + BulkWriteCommandResponseCursor( + 0, std::vector<BulkWriteReplyItem>(std::move(replies))), + numErrors); if (!retriedStmtIds.empty()) { reply.setRetriedStmtIds(std::move(retriedStmtIds)); } @@ -922,8 +934,10 @@ public: collectTelemetryMongod(opCtx, pinnedCursor, numRepliesInFirstBatch); replies.resize(numRepliesInFirstBatch); - auto reply = BulkWriteCommandReply(BulkWriteCommandResponseCursor( - cursorId, std::vector<BulkWriteReplyItem>(std::move(replies)))); + auto reply = BulkWriteCommandReply( + BulkWriteCommandResponseCursor(cursorId, + std::vector<BulkWriteReplyItem>(std::move(replies))), + numErrors); if (!retriedStmtIds.empty()) { reply.setRetriedStmtIds(std::move(retriedStmtIds)); } @@ -993,8 +1007,8 @@ BulkWriteReply performWrites(OperationContext* opCtx, const BulkWriteCommandRequ } }); - // Tell mongod what the shard and database versions are. This will cause writes to fail in case - // there is a mismatch in the mongos request provided versions and the local (shard's) + // Tell mongod what the shard and database versions are. This will cause writes to fail in + // case there is a mismatch in the mongos request provided versions and the local (shard's) // understanding of the version. for (const auto& nsInfo : req.getNsInfo()) { // TODO (SERVER-72767, SERVER-72804, SERVER-72805): Support timeseries collections. @@ -1045,7 +1059,8 @@ BulkWriteReply performWrites(OperationContext* opCtx, const BulkWriteCommandRequ invariant(batch.empty()); - return make_tuple(responses.getReplies(), responses.getRetriedStmtIds()); + return make_tuple( + responses.getReplies(), responses.getRetriedStmtIds(), responses.getNumErrors()); } } // namespace bulk_write diff --git a/src/mongo/db/commands/bulk_write.h b/src/mongo/db/commands/bulk_write.h index ca167f31fea..fa5849b278a 100644 --- a/src/mongo/db/commands/bulk_write.h +++ b/src/mongo/db/commands/bulk_write.h @@ -39,7 +39,7 @@ namespace bulk_write { using RetriedStmtIds = std::vector<int32_t>; using BulkWriteReplyItems = std::vector<BulkWriteReplyItem>; -using BulkWriteReply = std::tuple<BulkWriteReplyItems, RetriedStmtIds>; +using BulkWriteReply = std::tuple<BulkWriteReplyItems, RetriedStmtIds, int /* numErrors */>; BulkWriteReply performWrites(OperationContext* opCtx, const BulkWriteCommandRequest& req); diff --git a/src/mongo/db/commands/bulk_write.idl b/src/mongo/db/commands/bulk_write.idl index f64f1b12697..4deca00cbdb 100644 --- a/src/mongo/db/commands/bulk_write.idl +++ b/src/mongo/db/commands/bulk_write.idl @@ -255,6 +255,9 @@ structs: cursor: type: BulkWriteCommandResponseCursor stability: unstable + numErrors: + type: int + stability: unstable electionId: type: int optional: true diff --git a/src/mongo/s/commands/cluster_bulk_write_cmd.cpp b/src/mongo/s/commands/cluster_bulk_write_cmd.cpp index 1c62749c8c5..c14cba055f9 100644 --- a/src/mongo/s/commands/cluster_bulk_write_cmd.cpp +++ b/src/mongo/s/commands/cluster_bulk_write_cmd.cpp @@ -175,8 +175,10 @@ public: } if (numRepliesInFirstBatch == replyItems.size()) { collectTelemetryMongos(opCtx, reqObj, numRepliesInFirstBatch); - return BulkWriteCommandReply(BulkWriteCommandResponseCursor( - 0, std::vector<BulkWriteReplyItem>(std::move(replyItems)))); + return BulkWriteCommandReply( + BulkWriteCommandResponseCursor( + 0, std::vector<BulkWriteReplyItem>(std::move(replyItems))), + 0 /* TODO SERVER-76267: correctly populate numErrors */); } ccc->detachFromOperationContext(); @@ -197,8 +199,10 @@ public: CurOp::get(opCtx)->debug().cursorid = cursorId; replyItems.resize(numRepliesInFirstBatch); - return BulkWriteCommandReply(BulkWriteCommandResponseCursor( - cursorId, std::vector<BulkWriteReplyItem>(std::move(replyItems)))); + return BulkWriteCommandReply( + BulkWriteCommandResponseCursor( + cursorId, std::vector<BulkWriteReplyItem>(std::move(replyItems))), + 0 /* TODO SERVER-76267: correctly populate numErrors */); } }; |