Skip to content

Commit fff99b9

Browse files
committed
feat(NODE-6327): implement client bulk write types and builders
1 parent f525403 commit fff99b9

File tree

4 files changed

+976
-0
lines changed

4 files changed

+976
-0
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import { type Document } from '../../bson';
2+
import { DocumentSequence } from '../../cmap/commands';
3+
import { MongoInvalidArgumentError } from '../../error';
4+
import type {
5+
AnyClientBulkWriteModel,
6+
ClientBulkWriteOptions,
7+
ClientDeleteManyModel,
8+
ClientDeleteOneModel,
9+
ClientInsertOneModel,
10+
ClientReplaceOneModel,
11+
ClientUpdateManyModel,
12+
ClientUpdateOneModel
13+
} from './common';
14+
15+
/** @internal */
16+
export class ClientBulkWriteCommandBuilder {
17+
models: AnyClientBulkWriteModel[];
18+
options: ClientBulkWriteOptions;
19+
20+
/**
21+
* Create the command builder.
22+
* @param models - The client write models.
23+
*/
24+
constructor(models: AnyClientBulkWriteModel[], options: ClientBulkWriteOptions) {
25+
this.models = models;
26+
this.options = options;
27+
}
28+
29+
/**
30+
* Gets the errorsOnly value for the command, which is the inverse of the
31+
* user provided verboseResults option. Defaults to true.
32+
*/
33+
get errorsOnly(): boolean {
34+
if ('verboseResults' in this.options) {
35+
return !this.options.verboseResults;
36+
}
37+
return true;
38+
}
39+
40+
/**
41+
* Build the bulk write commands from the models.
42+
*/
43+
buildCommands(): Document[] {
44+
// The base command.
45+
const command: Document = {
46+
bulkWrite: 1,
47+
errorsOnly: this.errorsOnly,
48+
ordered: this.options.ordered ?? true
49+
};
50+
// Add bypassDocumentValidation if it was present in the options.
51+
if ('bypassDocumentValidation' in this.options) {
52+
command.bypassDocumentValidation = this.options.bypassDocumentValidation;
53+
}
54+
// Add let if it was present in the options.
55+
if ('let' in this.options) {
56+
command.let = this.options.let;
57+
}
58+
59+
// Iterate the models to build the ops and nsInfo fields.
60+
const operations = [];
61+
let currentNamespaceIndex = 0;
62+
const namespaces = new Map<string, number>();
63+
for (const model of this.models) {
64+
const ns = model.namespace;
65+
if (namespaces.has(ns)) {
66+
operations.push(builderFor(model).buildOperation(namespaces.get(ns) as number));
67+
} else {
68+
namespaces.set(ns, currentNamespaceIndex);
69+
operations.push(builderFor(model).buildOperation(currentNamespaceIndex));
70+
currentNamespaceIndex++;
71+
}
72+
}
73+
74+
const nsInfo = Array.from(namespaces.keys()).map(ns => {
75+
return { ns: ns };
76+
});
77+
command.ops = new DocumentSequence(operations);
78+
command.nsInfo = new DocumentSequence(nsInfo);
79+
return [command];
80+
}
81+
}
82+
83+
/** @internal */
84+
export interface OperationBuilder {
85+
buildOperation(index: number): Document;
86+
}
87+
88+
/**
89+
* Builds insert one operations given the model.
90+
* @internal
91+
*/
92+
export class InsertOneOperationBuilder implements OperationBuilder {
93+
model: ClientInsertOneModel;
94+
95+
/**
96+
* Instantiate the builder.
97+
* @param model - The client insert one model.
98+
*/
99+
constructor(model: ClientInsertOneModel) {
100+
this.model = model;
101+
}
102+
103+
/**
104+
* Build the operation.
105+
* @param index - The namespace index.
106+
* @returns the operation.
107+
*/
108+
buildOperation(index: number): Document {
109+
const document: Document = {
110+
insert: index,
111+
document: this.model.document
112+
};
113+
return document;
114+
}
115+
}
116+
117+
/** @internal */
118+
export class DeleteOneOperationBuilder implements OperationBuilder {
119+
model: ClientDeleteOneModel;
120+
121+
/**
122+
* Instantiate the builder.
123+
* @param model - The client delete one model.
124+
*/
125+
constructor(model: ClientDeleteOneModel) {
126+
this.model = model;
127+
}
128+
129+
/**
130+
* Build the operation.
131+
* @param index - The namespace index.
132+
* @returns the operation.
133+
*/
134+
buildOperation(index: number): Document {
135+
return createDeleteOperation(this.model, index, false);
136+
}
137+
}
138+
139+
/** @internal */
140+
export class DeleteManyOperationBuilder implements OperationBuilder {
141+
model: ClientDeleteManyModel;
142+
143+
/**
144+
* Instantiate the builder.
145+
* @param model - The client delete many model.
146+
*/
147+
constructor(model: ClientDeleteManyModel) {
148+
this.model = model;
149+
}
150+
151+
/**
152+
* Build the operation.
153+
* @param index - The namespace index.
154+
* @returns the operation.
155+
*/
156+
buildOperation(index: number): Document {
157+
return createDeleteOperation(this.model, index, true);
158+
}
159+
}
160+
161+
/**
162+
* Creates a delete operation based on the parameters.
163+
*/
164+
function createDeleteOperation(
165+
model: ClientDeleteOneModel | ClientDeleteManyModel,
166+
index: number,
167+
multi: boolean
168+
): Document {
169+
const document: Document = {
170+
delete: index,
171+
multi: multi,
172+
filter: model.filter
173+
};
174+
if (model.hint) {
175+
document.hint = model.hint;
176+
}
177+
if (model.collation) {
178+
document.collation = model.collation;
179+
}
180+
return document;
181+
}
182+
183+
/** @internal */
184+
export class UpdateOneOperationBuilder implements OperationBuilder {
185+
model: ClientUpdateOneModel;
186+
187+
/**
188+
* Instantiate the builder.
189+
* @param model - The client update one model.
190+
*/
191+
constructor(model: ClientUpdateOneModel) {
192+
this.model = model;
193+
}
194+
195+
/**
196+
* Build the operation.
197+
* @param index - The namespace index.
198+
* @returns the operation.
199+
*/
200+
buildOperation(index: number): Document {
201+
return createUpdateOperation(this.model, index, false);
202+
}
203+
}
204+
205+
/** @internal */
206+
export class UpdateManyOperationBuilder implements OperationBuilder {
207+
model: ClientUpdateManyModel;
208+
209+
/**
210+
* Instantiate the builder.
211+
* @param model - The client update many model.
212+
*/
213+
constructor(model: ClientUpdateManyModel) {
214+
this.model = model;
215+
}
216+
217+
/**
218+
* Build the operation.
219+
* @param index - The namespace index.
220+
* @returns the operation.
221+
*/
222+
buildOperation(index: number): Document {
223+
return createUpdateOperation(this.model, index, true);
224+
}
225+
}
226+
227+
/**
228+
* Creates a delete operation based on the parameters.
229+
*/
230+
function createUpdateOperation(
231+
model: ClientUpdateOneModel | ClientUpdateManyModel,
232+
index: number,
233+
multi: boolean
234+
): Document {
235+
const document: Document = {
236+
update: index,
237+
multi: multi,
238+
filter: model.filter,
239+
updateMods: model.update
240+
};
241+
if (model.hint) {
242+
document.hint = model.hint;
243+
}
244+
if (model.upsert) {
245+
document.upsert = model.upsert;
246+
}
247+
if (model.arrayFilters) {
248+
document.arrayFilters = model.arrayFilters;
249+
}
250+
return document;
251+
}
252+
253+
/** @internal */
254+
export class ReplaceOneOperationBuilder implements OperationBuilder {
255+
model: ClientReplaceOneModel;
256+
257+
/**
258+
* Instantiate the builder.
259+
* @param model - The client replace one model.
260+
*/
261+
constructor(model: ClientReplaceOneModel) {
262+
this.model = model;
263+
}
264+
265+
/**
266+
* Build the operation.
267+
* @param index - The namespace index.
268+
* @returns the operation.
269+
*/
270+
buildOperation(index: number): Document {
271+
const document: Document = {
272+
update: index,
273+
multi: false,
274+
filter: this.model.filter,
275+
updateMods: this.model.replacement
276+
};
277+
if (this.model.hint) {
278+
document.hint = this.model.hint;
279+
}
280+
if (this.model.upsert) {
281+
document.upsert = this.model.upsert;
282+
}
283+
return document;
284+
}
285+
}
286+
287+
const BUILDERS: Map<string, (model: AnyClientBulkWriteModel) => OperationBuilder> = new Map();
288+
BUILDERS.set('insertOne', model => new InsertOneOperationBuilder(model as ClientInsertOneModel));
289+
BUILDERS.set('deleteMany', model => new DeleteManyOperationBuilder(model as ClientDeleteManyModel));
290+
BUILDERS.set('deleteOne', model => new DeleteOneOperationBuilder(model as ClientDeleteOneModel));
291+
BUILDERS.set('updateMany', model => new UpdateManyOperationBuilder(model as ClientUpdateManyModel));
292+
BUILDERS.set('updateOne', model => new UpdateOneOperationBuilder(model as ClientUpdateOneModel));
293+
BUILDERS.set('replaceOne', model => new ReplaceOneOperationBuilder(model as ClientReplaceOneModel));
294+
295+
/** @internal */
296+
export function builderFor(model: AnyClientBulkWriteModel): OperationBuilder {
297+
const builder = BUILDERS.get(model.name)?.(model);
298+
if (!builder) {
299+
throw new MongoInvalidArgumentError(`Could not load builder for model ${model.name}`);
300+
}
301+
return builder;
302+
}

0 commit comments

Comments
 (0)