Skip to content

Commit 0f734ed

Browse files
authored
feat(shell-api): add Mongo bulkWrite API MONGOSH-1100 (#2416)
This API is meant to replace the existing bulk write API on the Collection as it supports a bulk write across multiple databases and collections in a single call.
1 parent 88f5fb1 commit 0f734ed

File tree

7 files changed

+247
-1
lines changed

7 files changed

+247
-1
lines changed

packages/i18n/src/locales/en_US.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,43 @@ const translations: Catalog = {
422422
},
423423
},
424424
},
425+
ClientBulkWriteResult: {
426+
help: {
427+
description: 'ClientBulkWriteResult Class',
428+
attributes: {
429+
acknowledged: {
430+
description: 'Acknowledged returned from the server',
431+
},
432+
insertedCount: {
433+
description: 'Number of documents inserted',
434+
},
435+
matchedCount: {
436+
description: 'Number of documents matched',
437+
},
438+
modifiedCount: {
439+
description: 'Number of documents modified',
440+
},
441+
deletedCount: {
442+
description: 'Number of documents deleted',
443+
},
444+
upsertedCount: {
445+
description: 'Number of documents upserted',
446+
},
447+
insertResults: {
448+
description:
449+
'The results of each individual insert operation that was successfully performed',
450+
},
451+
updateResults: {
452+
description:
453+
'The results of each individual update operation that was successfully performed',
454+
},
455+
deleteResults: {
456+
description:
457+
'The results of each individual delete operation that was successfully performed.',
458+
},
459+
},
460+
},
461+
},
425462
CommandResult: {
426463
help: {
427464
description: 'CommandResult Class',
@@ -2240,6 +2277,12 @@ const translations: Catalog = {
22402277
link: 'https://docs.mongodb.com/manual/reference/method/Mongo.startSession/',
22412278
description: 'Starts a session for the connection.',
22422279
},
2280+
bulkWrite: {
2281+
link: 'https://docs.mongodb.com/manual/reference/method/Mongo.bulkWrite',
2282+
description:
2283+
'Performs multiple write operations across databases and collections with controls for order of execution.',
2284+
example: 'db.getMongo().bulkWrite(operations, options)',
2285+
},
22432286
getCollection: {
22442287
link: 'https://docs.mongodb.com/manual/reference/method/Mongo.getCollection',
22452288
description:

packages/service-provider-core/src/all-transport-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ export type {
22
AggregateOptions,
33
AggregationCursor,
44
AnyBulkWriteOperation,
5+
AnyClientBulkWriteModel,
56
Batch,
67
BulkWriteOptions,
78
BulkWriteResult,
9+
ClientBulkWriteResult,
10+
ClientBulkWriteOptions,
811
ChangeStream,
912
ChangeStreamOptions,
1013
ClientSession,

packages/service-provider-core/src/writable.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import type {
1010
FindOneAndUpdateOptions,
1111
BulkWriteOptions,
1212
AnyBulkWriteOperation,
13+
AnyClientBulkWriteModel,
14+
ClientBulkWriteResult,
15+
ClientBulkWriteOptions,
1316
DeleteOptions,
1417
DeleteResult,
1518
InsertManyResult,
@@ -108,6 +111,18 @@ export default interface Writable {
108111
dbOptions?: DbOptions
109112
): Promise<BulkWriteResult>;
110113

114+
/**
115+
* Executes a client bulk write operation, available on server 8.0+.
116+
* @param models - The client bulk write models.
117+
* @param options - The bulk write options.
118+
*
119+
* @returns {Promise} The promise of the result.
120+
*/
121+
clientBulkWrite(
122+
models: AnyClientBulkWriteModel<Document>[],
123+
options: ClientBulkWriteOptions
124+
): Promise<ClientBulkWriteResult>;
125+
111126
/**
112127
* Delete multiple documents from the collection.
113128
*

packages/service-provider-node-driver/src/node-driver-service-provider.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ import type {
2323
AggregateOptions,
2424
AggregationCursor,
2525
AnyBulkWriteOperation,
26+
AnyClientBulkWriteModel,
2627
BulkWriteOptions,
2728
BulkWriteResult,
29+
ClientBulkWriteResult,
30+
ClientBulkWriteOptions,
2831
ClientSessionOptions,
2932
Collection,
3033
CountDocumentsOptions,
@@ -630,6 +633,17 @@ export class NodeDriverServiceProvider
630633
.bulkWrite(requests, options);
631634
}
632635

636+
/**
637+
* Executes a client bulk write operation, available on server 8.0+.
638+
*/
639+
clientBulkWrite(
640+
models: AnyClientBulkWriteModel<Document>[],
641+
options: ClientBulkWriteOptions = {}
642+
): Promise<ClientBulkWriteResult> {
643+
options = { ...this.baseCmdOptions, ...options };
644+
return this.mongoClient.bulkWrite(models, options);
645+
}
646+
633647
/**
634648
* Close the connection.
635649
*
@@ -997,7 +1011,7 @@ export class NodeDriverServiceProvider
9971011
options = { ...this.baseCmdOptions, ...options };
9981012
return this.db(database, dbOptions)
9991013
.collection(collection)
1000-
.replaceOne(filter, replacement, options) as Promise<UpdateResult>;
1014+
.replaceOne(filter, replacement, options);
10011015
// `as UpdateResult` because we know we didn't request .explain() here.
10021016
}
10031017

packages/shell-api/src/mongo.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
startSharedTestServer,
3636
} from '../../../testing/integration-testing-hooks';
3737
import { dummyOptions } from './helpers.spec';
38+
import { ClientBulkWriteResult } from './result';
3839

3940
const sampleOpts = {
4041
causalConsistency: false,
@@ -1015,6 +1016,76 @@ describe('Mongo', function () {
10151016
).toArray();
10161017
});
10171018
});
1019+
1020+
context('post-8.0', function () {
1021+
skipIfServerVersion(testServer, '< 8.0');
1022+
let mongo: Mongo;
1023+
1024+
describe('bulkWrite', function () {
1025+
beforeEach(async function () {
1026+
mongo = await instanceState.shellApi.Mongo(uri, undefined, {
1027+
api: { version: '1' },
1028+
});
1029+
});
1030+
1031+
it('should allow inserts across collections and databases', async function () {
1032+
expect(
1033+
await mongo.bulkWrite([
1034+
{
1035+
name: 'insertOne',
1036+
namespace: 'db.authors',
1037+
document: { name: 'King' },
1038+
},
1039+
{
1040+
name: 'deleteOne',
1041+
namespace: 'db.authors',
1042+
filter: { name: 'King' },
1043+
},
1044+
{
1045+
name: 'insertOne',
1046+
namespace: 'db.moreAuthors',
1047+
document: { name: 'Queen' },
1048+
},
1049+
{
1050+
name: 'insertOne',
1051+
namespace: 'otherDb.authors',
1052+
document: { name: 'Prince' },
1053+
},
1054+
])
1055+
).deep.equals(
1056+
new ClientBulkWriteResult({
1057+
acknowledged: true,
1058+
insertedCount: 3,
1059+
upsertedCount: 0,
1060+
matchedCount: 0,
1061+
modifiedCount: 0,
1062+
deletedCount: 1,
1063+
insertResults: undefined,
1064+
updateResults: undefined,
1065+
deleteResults: undefined,
1066+
})
1067+
);
1068+
1069+
expect(
1070+
await mongo.getDB('db').getCollection('authors').count()
1071+
).equals(0);
1072+
1073+
expect(
1074+
await mongo
1075+
.getDB('db')
1076+
.getCollection('moreAuthors')
1077+
.count({ name: 'Queen' })
1078+
).equals(1);
1079+
1080+
expect(
1081+
await mongo
1082+
.getDB('otherDb')
1083+
.getCollection('authors')
1084+
.count({ name: 'Prince' })
1085+
).equals(1);
1086+
});
1087+
});
1088+
});
10181089
});
10191090
});
10201091
});

packages/shell-api/src/mongo.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import type {
3636
ServerApi,
3737
ServerApiVersion,
3838
WriteConcern,
39+
AnyClientBulkWriteModel,
40+
ClientBulkWriteOptions,
3941
} from '@mongosh/service-provider-core';
4042
import type { ConnectionInfo } from '@mongosh/arg-parser';
4143
import {
@@ -45,6 +47,7 @@ import {
4547
import type Collection from './collection';
4648
import Database from './database';
4749
import type ShellInstanceState from './shell-instance-state';
50+
import { ClientBulkWriteResult } from './result';
4851
import { CommandResult } from './result';
4952
import { redactURICredentials } from '@mongosh/history';
5053
import { asPrintable, ServerVersions, Topologies } from './enums';
@@ -365,6 +368,40 @@ export default class Mongo extends ShellApiClass {
365368
return await this._listDatabases(options);
366369
}
367370

371+
@returnsPromise
372+
@serverVersions(['8.0.0', ServerVersions.latest])
373+
@apiVersions([1])
374+
async bulkWrite(
375+
models: AnyClientBulkWriteModel<Document>[],
376+
options: ClientBulkWriteOptions = {}
377+
): Promise<ClientBulkWriteResult> {
378+
this._emitMongoApiCall('bulkWrite', { options });
379+
380+
const {
381+
acknowledged,
382+
insertedCount,
383+
matchedCount,
384+
modifiedCount,
385+
deletedCount,
386+
upsertedCount,
387+
insertResults,
388+
updateResults,
389+
deleteResults,
390+
} = await this._serviceProvider.clientBulkWrite(models, options);
391+
392+
return new ClientBulkWriteResult({
393+
acknowledged,
394+
insertedCount,
395+
matchedCount,
396+
modifiedCount,
397+
deletedCount,
398+
upsertedCount,
399+
insertResults,
400+
updateResults,
401+
deleteResults,
402+
});
403+
}
404+
368405
@returnsPromise
369406
@apiVersions([1])
370407
async getDBNames(options: ListDatabasesOptions = {}): Promise<string[]> {

packages/shell-api/src/result.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,69 @@ export class CommandResult<T = unknown> extends ShellApiValueClass {
2525
}
2626
}
2727

28+
export type ClientInsertResult = {
29+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
30+
insertedId: any;
31+
};
32+
33+
export type ClientUpdateResult = {
34+
matchedCount: number;
35+
modifiedCount: number;
36+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
37+
upsertedId?: any;
38+
didUpsert: boolean;
39+
};
40+
41+
export type ClientDeleteResult = {
42+
deletedCount: number;
43+
};
44+
45+
@shellApiClassDefault
46+
export class ClientBulkWriteResult extends ShellApiValueClass {
47+
acknowledged: boolean;
48+
insertedCount: number;
49+
matchedCount: number;
50+
modifiedCount: number;
51+
deletedCount: number;
52+
upsertedCount: number;
53+
insertResults?: ReadonlyMap<number, ClientInsertResult>;
54+
updateResults?: ReadonlyMap<number, ClientUpdateResult>;
55+
deleteResults?: ReadonlyMap<number, ClientDeleteResult>;
56+
57+
constructor({
58+
acknowledged,
59+
insertedCount,
60+
matchedCount,
61+
modifiedCount,
62+
deletedCount,
63+
upsertedCount,
64+
insertResults,
65+
updateResults,
66+
deleteResults,
67+
}: {
68+
acknowledged: boolean;
69+
insertedCount: number;
70+
matchedCount: number;
71+
modifiedCount: number;
72+
deletedCount: number;
73+
upsertedCount: number;
74+
insertResults?: ReadonlyMap<number, ClientInsertResult>;
75+
updateResults?: ReadonlyMap<number, ClientUpdateResult>;
76+
deleteResults?: ReadonlyMap<number, ClientDeleteResult>;
77+
}) {
78+
super();
79+
this.acknowledged = acknowledged;
80+
this.insertedCount = insertedCount;
81+
this.matchedCount = matchedCount;
82+
this.modifiedCount = modifiedCount;
83+
this.deletedCount = deletedCount;
84+
this.upsertedCount = upsertedCount;
85+
this.insertResults = insertResults;
86+
this.updateResults = updateResults;
87+
this.deleteResults = deleteResults;
88+
}
89+
}
90+
2891
@shellApiClassDefault
2992
export class BulkWriteResult extends ShellApiValueClass {
3093
acknowledged: boolean;

0 commit comments

Comments
 (0)