Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions packages/i18n/src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,43 @@ const translations: Catalog = {
},
},
},
ClientBulkWriteResult: {
help: {
description: 'ClientBulkWriteResult Class',
attributes: {
acknowledged: {
description: 'Acknowledged returned from the server',
},
insertedCount: {
description: 'Number of documents inserted',
},
matchedCount: {
description: 'Number of documents matched',
},
modifiedCount: {
description: 'Number of documents modified',
},
deletedCount: {
description: 'Number of documents deleted',
},
upsertedCount: {
description: 'Number of documents upserted',
},
insertResults: {
description:
'The results of each individual insert operation that was successfully performed',
},
updateResults: {
description:
'The results of each individual update operation that was successfully performed',
},
deleteResults: {
description:
'The results of each individual delete operation that was successfully performed.',
},
},
},
},
CommandResult: {
help: {
description: 'CommandResult Class',
Expand Down Expand Up @@ -2240,6 +2277,12 @@ const translations: Catalog = {
link: 'https://docs.mongodb.com/manual/reference/method/Mongo.startSession/',
description: 'Starts a session for the connection.',
},
bulkWrite: {
link: 'https://docs.mongodb.com/manual/reference/method/Mongo.bulkWrite',
description:
'Performs multiple write operations across databases and collections with controls for order of execution.',
example: 'db.getMongo().bulkWrite(operations, options)',
},
getCollection: {
link: 'https://docs.mongodb.com/manual/reference/method/Mongo.getCollection',
description:
Expand Down
3 changes: 3 additions & 0 deletions packages/service-provider-core/src/all-transport-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ export type {
AggregateOptions,
AggregationCursor,
AnyBulkWriteOperation,
AnyClientBulkWriteModel,
Batch,
BulkWriteOptions,
BulkWriteResult,
ClientBulkWriteResult,
ClientBulkWriteOptions,
ChangeStream,
ChangeStreamOptions,
ClientSession,
Expand Down
15 changes: 15 additions & 0 deletions packages/service-provider-core/src/writable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import type {
FindOneAndUpdateOptions,
BulkWriteOptions,
AnyBulkWriteOperation,
AnyClientBulkWriteModel,
ClientBulkWriteResult,
ClientBulkWriteOptions,
DeleteOptions,
DeleteResult,
InsertManyResult,
Expand Down Expand Up @@ -108,6 +111,18 @@ export default interface Writable {
dbOptions?: DbOptions
): Promise<BulkWriteResult>;

/**
* Executes a client bulk write operation, available on server 8.0+.
* @param models - The client bulk write models.
* @param options - The bulk write options.
*
* @returns {Promise} The promise of the result.
*/
clientBulkWrite(
models: AnyClientBulkWriteModel<Document>[],
options: ClientBulkWriteOptions
): Promise<ClientBulkWriteResult>;

/**
* Delete multiple documents from the collection.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import type {
AggregateOptions,
AggregationCursor,
AnyBulkWriteOperation,
AnyClientBulkWriteModel,
BulkWriteOptions,
BulkWriteResult,
ClientBulkWriteResult,
ClientBulkWriteOptions,
ClientSessionOptions,
Collection,
CountDocumentsOptions,
Expand Down Expand Up @@ -630,6 +633,17 @@ export class NodeDriverServiceProvider
.bulkWrite(requests, options);
}

/**
* Executes a client bulk write operation, available on server 8.0+.
*/
clientBulkWrite(
models: AnyClientBulkWriteModel<Document>[],
options: ClientBulkWriteOptions = {}
): Promise<ClientBulkWriteResult> {
options = { ...this.baseCmdOptions, ...options };
return this.mongoClient.bulkWrite(models, options);
}

/**
* Close the connection.
*
Expand Down Expand Up @@ -997,7 +1011,7 @@ export class NodeDriverServiceProvider
options = { ...this.baseCmdOptions, ...options };
return this.db(database, dbOptions)
.collection(collection)
.replaceOne(filter, replacement, options) as Promise<UpdateResult>;
.replaceOne(filter, replacement, options);
// `as UpdateResult` because we know we didn't request .explain() here.
}

Expand Down
71 changes: 71 additions & 0 deletions packages/shell-api/src/mongo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
startSharedTestServer,
} from '../../../testing/integration-testing-hooks';
import { dummyOptions } from './helpers.spec';
import { ClientBulkWriteResult } from './result';

const sampleOpts = {
causalConsistency: false,
Expand Down Expand Up @@ -1015,6 +1016,76 @@ describe('Mongo', function () {
).toArray();
});
});

context('post-8.0', function () {
skipIfServerVersion(testServer, '< 8.0');
let mongo: Mongo;

describe('bulkWrite', function () {
beforeEach(async function () {
mongo = await instanceState.shellApi.Mongo(uri, undefined, {
api: { version: '1' },
});
});

it('should allow inserts across collections and databases', async function () {
expect(
await mongo.bulkWrite([
{
name: 'insertOne',
namespace: 'db.authors',
document: { name: 'King' },
},
{
name: 'deleteOne',
namespace: 'db.authors',
filter: { name: 'King' },
},
{
name: 'insertOne',
namespace: 'db.moreAuthors',
document: { name: 'Queen' },
},
{
name: 'insertOne',
namespace: 'otherDb.authors',
document: { name: 'Prince' },
},
])
).deep.equals(
new ClientBulkWriteResult({
acknowledged: true,
insertedCount: 3,
upsertedCount: 0,
matchedCount: 0,
modifiedCount: 0,
deletedCount: 1,
insertResults: undefined,
updateResults: undefined,
deleteResults: undefined,
})
);

expect(
await mongo.getDB('db').getCollection('authors').count()
).equals(0);

expect(
await mongo
.getDB('db')
.getCollection('moreAuthors')
.count({ name: 'Queen' })
).equals(1);

expect(
await mongo
.getDB('otherDb')
.getCollection('authors')
.count({ name: 'Prince' })
).equals(1);
});
});
});
});
});
});
37 changes: 37 additions & 0 deletions packages/shell-api/src/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import type {
ServerApi,
ServerApiVersion,
WriteConcern,
AnyClientBulkWriteModel,
ClientBulkWriteOptions,
} from '@mongosh/service-provider-core';
import type { ConnectionInfo } from '@mongosh/arg-parser';
import {
Expand All @@ -45,6 +47,7 @@ import {
import type Collection from './collection';
import Database from './database';
import type ShellInstanceState from './shell-instance-state';
import { ClientBulkWriteResult } from './result';
import { CommandResult } from './result';
import { redactURICredentials } from '@mongosh/history';
import { asPrintable, ServerVersions, Topologies } from './enums';
Expand Down Expand Up @@ -365,6 +368,40 @@ export default class Mongo extends ShellApiClass {
return await this._listDatabases(options);
}

@returnsPromise
@serverVersions(['8.0.0', ServerVersions.latest])
@apiVersions([1])
async bulkWrite(
models: AnyClientBulkWriteModel<Document>[],
options: ClientBulkWriteOptions = {}
): Promise<ClientBulkWriteResult> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've typically wrapped result types in our own types so that we're a bit more independent when it comes to server changes, see result.ts – I'd consider doing that here as well if we can (this mostly already matches our existing BulkWriteResult as well?)

this._emitMongoApiCall('bulkWrite', { options });

const {
acknowledged,
insertedCount,
matchedCount,
modifiedCount,
deletedCount,
upsertedCount,
insertResults,
updateResults,
deleteResults,
} = await this._serviceProvider.clientBulkWrite(models, options);

return new ClientBulkWriteResult({
acknowledged,
insertedCount,
matchedCount,
modifiedCount,
deletedCount,
upsertedCount,
insertResults,
updateResults,
deleteResults,
});
}

@returnsPromise
@apiVersions([1])
async getDBNames(options: ListDatabasesOptions = {}): Promise<string[]> {
Expand Down
63 changes: 63 additions & 0 deletions packages/shell-api/src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,69 @@ export class CommandResult<T = unknown> extends ShellApiValueClass {
}
}

export type ClientInsertResult = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
insertedId: any;
};

export type ClientUpdateResult = {
matchedCount: number;
modifiedCount: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
upsertedId?: any;
didUpsert: boolean;
};

export type ClientDeleteResult = {
deletedCount: number;
};

@shellApiClassDefault
export class ClientBulkWriteResult extends ShellApiValueClass {
acknowledged: boolean;
insertedCount: number;
matchedCount: number;
modifiedCount: number;
deletedCount: number;
upsertedCount: number;
insertResults?: ReadonlyMap<number, ClientInsertResult>;
updateResults?: ReadonlyMap<number, ClientUpdateResult>;
deleteResults?: ReadonlyMap<number, ClientDeleteResult>;

constructor({
acknowledged,
insertedCount,
matchedCount,
modifiedCount,
deletedCount,
upsertedCount,
insertResults,
updateResults,
deleteResults,
}: {
acknowledged: boolean;
insertedCount: number;
matchedCount: number;
modifiedCount: number;
deletedCount: number;
upsertedCount: number;
insertResults?: ReadonlyMap<number, ClientInsertResult>;
updateResults?: ReadonlyMap<number, ClientUpdateResult>;
deleteResults?: ReadonlyMap<number, ClientDeleteResult>;
}) {
super();
this.acknowledged = acknowledged;
this.insertedCount = insertedCount;
this.matchedCount = matchedCount;
this.modifiedCount = modifiedCount;
this.deletedCount = deletedCount;
this.upsertedCount = upsertedCount;
this.insertResults = insertResults;
this.updateResults = updateResults;
this.deleteResults = deleteResults;
}
}

@shellApiClassDefault
export class BulkWriteResult extends ShellApiValueClass {
acknowledged: boolean;
Expand Down