Skip to content

Commit 7fde8dd

Browse files
authored
feat(NODE-6409): new errors for unacknowledged bulk writes (#4276)
1 parent a473de9 commit 7fde8dd

File tree

6 files changed

+198
-6
lines changed

6 files changed

+198
-6
lines changed

src/operations/client_bulk_write/executor.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ClientBulkWriteCursor } from '../../cursor/client_bulk_write_cursor';
22
import {
33
MongoClientBulkWriteError,
44
MongoClientBulkWriteExecutionError,
5+
MongoInvalidArgumentError,
56
MongoServerError
67
} from '../../error';
78
import { type MongoClient } from '../../mongo_client';
@@ -53,6 +54,20 @@ export class ClientBulkWriteExecutor {
5354
if (!this.options.writeConcern) {
5455
this.options.writeConcern = WriteConcern.fromOptions(this.client.options);
5556
}
57+
58+
if (this.options.writeConcern?.w === 0) {
59+
if (this.options.verboseResults) {
60+
throw new MongoInvalidArgumentError(
61+
'Cannot request unacknowledged write concern and verbose results'
62+
);
63+
}
64+
65+
if (this.options.ordered) {
66+
throw new MongoInvalidArgumentError(
67+
'Cannot request unacknowledged write concern and ordered writes'
68+
);
69+
}
70+
}
5671
}
5772

5873
/**

test/integration/crud/crud.prose.test.ts

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,8 @@ describe('CRUD Prose Spec Tests', () => {
738738
async test() {
739739
const error = await client
740740
.bulkWrite([{ name: 'insertOne', namespace: 'db.coll', document: document }], {
741-
writeConcern: { w: 0 }
741+
writeConcern: { w: 0 },
742+
ordered: false
742743
})
743744
.catch(error => error);
744745
expect(error.message).to.include('Client bulk write operation ops of length');
@@ -763,7 +764,7 @@ describe('CRUD Prose Spec Tests', () => {
763764
const error = await client
764765
.bulkWrite(
765766
[{ name: 'replaceOne', namespace: 'db.coll', filter: {}, replacement: document }],
766-
{ writeConcern: { w: 0 } }
767+
{ writeConcern: { w: 0 }, ordered: false }
767768
)
768769
.catch(error => error);
769770
expect(error.message).to.include('Client bulk write operation ops of length');
@@ -1079,4 +1080,89 @@ describe('CRUD Prose Spec Tests', () => {
10791080
expect(command).to.have.property('maxTimeMS', 2000);
10801081
});
10811082
});
1083+
1084+
describe('15. `MongoClient.bulkWrite` with unacknowledged write concern uses `w:0` for all batches', function () {
1085+
// This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless.
1086+
// If testing with a sharded cluster, only connect to one mongos. This is intended to ensure the `countDocuments` operation
1087+
// uses the same connection as the `bulkWrite` to get the correct connection count. (See
1088+
// [DRIVERS-2921](https://jira.mongodb.org/browse/DRIVERS-2921)).
1089+
// Construct a `MongoClient` (referred to as `client`) with
1090+
// [command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe
1091+
// CommandStartedEvents. Perform a `hello` command using `client` and record the `maxBsonObjectSize` and
1092+
// `maxMessageSizeBytes` values in the response.
1093+
// Construct a `MongoCollection` (referred to as `coll`) for the collection "db.coll". Drop `coll`.
1094+
// Use the `create` command to create "db.coll" to workaround [SERVER-95537](https://jira.mongodb.org/browse/SERVER-95537).
1095+
// Construct the following write model (referred to as `model`):
1096+
// InsertOne: {
1097+
// "namespace": "db.coll",
1098+
// "document": { "a": "b".repeat(maxBsonObjectSize - 500) }
1099+
// }
1100+
// Construct a list of write models (referred to as `models`) with `model` repeated
1101+
// `maxMessageSizeBytes / maxBsonObjectSize + 1` times.
1102+
// Call `client.bulkWrite` with `models`. Pass `BulkWriteOptions` with `ordered` set to `false` and `writeConcern` set to
1103+
// an unacknowledged write concern. Assert no error occurred. Assert the result indicates the write was unacknowledged.
1104+
// Assert that two CommandStartedEvents (referred to as `firstEvent` and `secondEvent`) were observed for the `bulkWrite`
1105+
// command. Assert that the length of `firstEvent.command.ops` is `maxMessageSizeBytes / maxBsonObjectSize`. Assert that
1106+
// the length of `secondEvent.command.ops` is 1. If the driver exposes `operationId`s in its CommandStartedEvents, assert
1107+
// that `firstEvent.operationId` is equal to `secondEvent.operationId`. Assert both commands include
1108+
// `writeConcern: {w: 0}`.
1109+
// To force completion of the `w:0` writes, execute `coll.countDocuments` and expect the returned count is
1110+
// `maxMessageSizeBytes / maxBsonObjectSize + 1`. This is intended to avoid incomplete writes interfering with other tests
1111+
// that may use this collection.
1112+
let client: MongoClient;
1113+
let maxBsonObjectSize;
1114+
let maxMessageSizeBytes;
1115+
let numModels;
1116+
let models: AnyClientBulkWriteModel[] = [];
1117+
const commands: CommandStartedEvent[] = [];
1118+
1119+
beforeEach(async function () {
1120+
const uri = this.configuration.url({
1121+
useMultipleMongoses: false
1122+
});
1123+
client = this.configuration.newClient(uri, { monitorCommands: true });
1124+
await client.connect();
1125+
await client
1126+
.db('db')
1127+
.collection('coll')
1128+
.drop()
1129+
.catch(() => null);
1130+
await client.db('db').createCollection('coll');
1131+
const hello = await client.db('admin').command({ hello: 1 });
1132+
maxBsonObjectSize = hello.maxBsonObjectSize;
1133+
maxMessageSizeBytes = hello.maxMessageSizeBytes;
1134+
numModels = Math.floor(maxMessageSizeBytes / maxBsonObjectSize) + 1;
1135+
models = Array.from({ length: numModels }, () => {
1136+
return {
1137+
name: 'insertOne',
1138+
namespace: 'db.coll',
1139+
document: {
1140+
a: 'b'.repeat(maxBsonObjectSize - 500)
1141+
}
1142+
};
1143+
});
1144+
1145+
client.on('commandStarted', filterForCommands('bulkWrite', commands));
1146+
commands.length = 0;
1147+
});
1148+
1149+
afterEach(async function () {
1150+
await client.close();
1151+
});
1152+
1153+
it('performs all writes unacknowledged', {
1154+
metadata: { requires: { mongodb: '>=8.0.0', serverless: 'forbid' } },
1155+
async test() {
1156+
const result = await client.bulkWrite(models, { ordered: false, writeConcern: { w: 0 } });
1157+
expect(result).to.deep.equal({ ok: 1 });
1158+
expect(commands.length).to.equal(2);
1159+
expect(commands[0].command.ops.length).to.equal(numModels - 1);
1160+
expect(commands[0].command.writeConcern.w).to.equal(0);
1161+
expect(commands[1].command.ops.length).to.equal(1);
1162+
expect(commands[1].command.writeConcern.w).to.equal(0);
1163+
const count = await client.db('db').collection('coll').countDocuments();
1164+
expect(count).to.equal(numModels);
1165+
}
1166+
});
1167+
});
10821168
});

test/spec/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"schemaVersion": "1.7",
44
"runOnRequirements": [
55
{
6-
"minServerVersion": "8.0"
6+
"minServerVersion": "8.0",
7+
"serverless": "forbid"
78
}
89
],
910
"createEntities": [
@@ -90,7 +91,8 @@
9091
}
9192
}
9293
}
93-
]
94+
],
95+
"ordered": false
9496
},
9597
"expectResult": {
9698
"insertedCount": {
@@ -157,7 +159,7 @@
157159
"command": {
158160
"bulkWrite": 1,
159161
"errorsOnly": true,
160-
"ordered": true,
162+
"ordered": false,
161163
"ops": [
162164
{
163165
"insert": 0,

test/spec/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ schemaVersion: "1.7"
44

55
runOnRequirements:
66
- minServerVersion: "8.0"
7+
serverless: forbid
78

89
createEntities:
910
- client:
@@ -49,6 +50,7 @@ tests:
4950
namespace: *namespace
5051
filter: { _id: 3 }
5152
update: { $set: { x: 333 } }
53+
ordered: false
5254
expectResult:
5355
insertedCount:
5456
$$unsetOrMatches: 0
@@ -88,7 +90,7 @@ tests:
8890
command:
8991
bulkWrite: 1
9092
errorsOnly: true
91-
ordered: true
93+
ordered: false
9294
ops:
9395
- insert: 0
9496
document: { _id: 4, x: 44 }

test/spec/crud/unified/client-bulkWrite-errors.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,64 @@
450450
}
451451
}
452452
]
453+
},
454+
{
455+
"description": "Requesting unacknowledged write with verboseResults is a client-side error",
456+
"operations": [
457+
{
458+
"name": "clientBulkWrite",
459+
"object": "client0",
460+
"arguments": {
461+
"models": [
462+
{
463+
"insertOne": {
464+
"namespace": "crud-tests.coll0",
465+
"document": {
466+
"_id": 10
467+
}
468+
}
469+
}
470+
],
471+
"verboseResults": true,
472+
"ordered": false,
473+
"writeConcern": {
474+
"w": 0
475+
}
476+
},
477+
"expectError": {
478+
"isClientError": true,
479+
"errorContains": "Cannot request unacknowledged write concern and verbose results"
480+
}
481+
}
482+
]
483+
},
484+
{
485+
"description": "Requesting unacknowledged write with ordered is a client-side error",
486+
"operations": [
487+
{
488+
"name": "clientBulkWrite",
489+
"object": "client0",
490+
"arguments": {
491+
"models": [
492+
{
493+
"insertOne": {
494+
"namespace": "crud-tests.coll0",
495+
"document": {
496+
"_id": 10
497+
}
498+
}
499+
}
500+
],
501+
"writeConcern": {
502+
"w": 0
503+
}
504+
},
505+
"expectError": {
506+
"isClientError": true,
507+
"errorContains": "Cannot request unacknowledged write concern and ordered writes"
508+
}
509+
}
510+
]
453511
}
454512
]
455513
}

test/spec/crud/unified/client-bulkWrite-errors.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,32 @@ tests:
239239
verboseResults: true
240240
expectError:
241241
isClientError: true
242+
- description: "Requesting unacknowledged write with verboseResults is a client-side error"
243+
operations:
244+
- name: clientBulkWrite
245+
object: *client0
246+
arguments:
247+
models:
248+
- insertOne:
249+
namespace: *namespace
250+
document: { _id: 10 }
251+
verboseResults: true
252+
ordered: false
253+
writeConcern: { w: 0 }
254+
expectError:
255+
isClientError: true
256+
errorContains: "Cannot request unacknowledged write concern and verbose results"
257+
- description: "Requesting unacknowledged write with ordered is a client-side error"
258+
operations:
259+
- name: clientBulkWrite
260+
object: *client0
261+
arguments:
262+
models:
263+
- insertOne:
264+
namespace: *namespace
265+
document: { _id: 10 }
266+
# Omit `ordered` option. Defaults to true.
267+
writeConcern: { w: 0 }
268+
expectError:
269+
isClientError: true
270+
errorContains: "Cannot request unacknowledged write concern and ordered writes"

0 commit comments

Comments
 (0)