Skip to content

Commit 6d65ae7

Browse files
authored
feat(NODE-6327): new client bulk write types and builders (#4205)
1 parent 40ace73 commit 6d65ae7

File tree

4 files changed

+858
-0
lines changed

4 files changed

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

0 commit comments

Comments
 (0)