Skip to content

Commit 2e36815

Browse files
authored
refactor: consolidate options inheritance for bulk ops (#2617)
Bulk operations now use resolveOptions to inherit options properties. Resolution of the bsonOptions and readPreference properties was moved to OperationBase, and the readPreference option was moved to OperationOptions. NODE-2870
1 parent 812f68d commit 2e36815

File tree

9 files changed

+52
-82
lines changed

9 files changed

+52
-82
lines changed

src/bulk/common.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { PromiseProvider } from '../promise_provider';
22
import { Long, ObjectId, Document, BSONSerializeOptions, resolveBSONOptions } from '../bson';
33
import { MongoError, MongoWriteConcernError, AnyError } from '../error';
44
import {
5-
applyWriteConcern,
65
applyRetryableWrites,
76
executeLegacyOperation,
87
hasAtomicOperators,
98
Callback,
109
MongoDBNamespace,
1110
maxWireVersion,
12-
getTopology
11+
getTopology,
12+
resolveOptions
1313
} from '../utils';
1414
import { executeOperation } from '../operations/execute_operation';
1515
import { InsertOperation } from '../operations/insert';
@@ -571,14 +571,10 @@ function executeCommands(
571571
executeCommands(bulkOperation, options, callback);
572572
}
573573

574-
const finalOptions = Object.assign(
575-
{ ordered: bulkOperation.isOrdered },
576-
bulkOperation.bsonOptions,
577-
options
578-
);
579-
if (bulkOperation.s.writeConcern != null) {
580-
finalOptions.writeConcern = bulkOperation.s.writeConcern;
581-
}
574+
const finalOptions = resolveOptions(bulkOperation, {
575+
...options,
576+
ordered: bulkOperation.isOrdered
577+
});
582578

583579
if (finalOptions.bypassDocumentValidation !== true) {
584580
delete finalOptions.bypassDocumentValidation;
@@ -935,10 +931,9 @@ export abstract class BulkOperationBase {
935931
// + 1 bytes for null terminator
936932
const maxKeySize = (maxWriteBatchSize - 1).toString(10).length + 2;
937933

938-
// Final options for retryable writes and write concern
934+
// Final options for retryable writes
939935
let finalOptions = Object.assign({}, options);
940936
finalOptions = applyRetryableWrites(finalOptions, collection.s.db);
941-
finalOptions = applyWriteConcern(finalOptions, { collection: collection }, options);
942937

943938
// Final results
944939
const bulkResult: BulkResult = {
@@ -983,7 +978,7 @@ export abstract class BulkOperationBase {
983978
// Options
984979
options: finalOptions,
985980
// BSON options
986-
bsonOptions: resolveBSONOptions(options, collection),
981+
bsonOptions: resolveBSONOptions(options),
987982
// Current operation
988983
currentOp,
989984
// Executed
@@ -1169,19 +1164,18 @@ export abstract class BulkOperationBase {
11691164
return this.s.bsonOptions;
11701165
}
11711166

1167+
get writeConcern(): WriteConcern | undefined {
1168+
return this.s.writeConcern;
1169+
}
1170+
11721171
/** An internal helper method. Do not invoke directly. Will be going away in the future */
1173-
execute(
1174-
_writeConcern?: WriteConcern,
1175-
options?: BulkWriteOptions,
1176-
callback?: Callback<BulkWriteResult>
1177-
): Promise<void> | void {
1172+
execute(options?: BulkWriteOptions, callback?: Callback<BulkWriteResult>): Promise<void> | void {
11781173
if (typeof options === 'function') (callback = options), (options = {});
11791174
options = options || {};
11801175

1181-
if (typeof _writeConcern === 'function') {
1182-
callback = _writeConcern as Callback;
1183-
} else if (_writeConcern && typeof _writeConcern === 'object') {
1184-
this.s.writeConcern = _writeConcern;
1176+
const writeConcern = WriteConcern.fromOptions(options);
1177+
if (writeConcern) {
1178+
this.s.writeConcern = writeConcern;
11851179
}
11861180

11871181
if (this.s.executed) {

src/collection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ export class Collection implements OperationParent {
314314

315315
return executeOperation(
316316
getTopology(this),
317-
new InsertManyOperation(this, docs, options),
317+
new InsertManyOperation(this, docs, resolveOptions(this, options)),
318318
callback
319319
);
320320
}
@@ -374,7 +374,7 @@ export class Collection implements OperationParent {
374374

375375
return executeOperation(
376376
getTopology(this),
377-
new BulkWriteOperation(this, operations, options),
377+
new BulkWriteOperation(this, operations, resolveOptions(this, options)),
378378
callback
379379
);
380380
}
@@ -1311,12 +1311,12 @@ export class Collection implements OperationParent {
13111311

13121312
/** Initiate an Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order. */
13131313
initializeUnorderedBulkOp(options?: BulkWriteOptions): any {
1314-
return new UnorderedBulkOperation(this, options ?? {});
1314+
return new UnorderedBulkOperation(this, resolveOptions(this, options));
13151315
}
13161316

13171317
/** Initiate an In order bulk write operation. Operations will be serially executed in the order they are added, creating a new operation for each switch in types. */
13181318
initializeOrderedBulkOp(options?: BulkWriteOptions): any {
1319-
return new OrderedBulkOperation(this, options ?? {});
1319+
return new OrderedBulkOperation(this, resolveOptions(this, options));
13201320
}
13211321

13221322
/** Get the db scoped logger */

src/operations/bulk_write.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { applyRetryableWrites, applyWriteConcern, Callback } from '../utils';
2-
import { OperationBase } from './operation';
3-
import { resolveBSONOptions } from '../bson';
4-
import { WriteConcern } from '../write_concern';
1+
import { applyRetryableWrites, Callback } from '../utils';
2+
import { Aspect, defineAspects, OperationBase } from './operation';
53
import type { Collection } from '../collection';
64
import type {
75
BulkOperationBase,
@@ -25,9 +23,6 @@ export class BulkWriteOperation extends OperationBase<BulkWriteOptions, BulkWrit
2523

2624
this.collection = collection;
2725
this.operations = operations;
28-
29-
// Assign BSON serialize options to OperationBase, preferring options over collection options
30-
this.bsonOptions = resolveBSONOptions(options, collection);
3126
}
3227

3328
execute(server: Server, callback: Callback<BulkWriteResult>): void {
@@ -50,15 +45,11 @@ export class BulkWriteOperation extends OperationBase<BulkWriteOptions, BulkWrit
5045
return callback(err);
5146
}
5247

53-
// Final options for retryable writes and write concern
5448
let finalOptions = Object.assign({}, options);
5549
finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
56-
finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
57-
58-
const writeCon = WriteConcern.fromOptions(finalOptions);
5950

6051
// Execute the bulk
61-
bulk.execute(writeCon, finalOptions, (err, r) => {
52+
bulk.execute(finalOptions, (err, r) => {
6253
// We have connection level error
6354
if (!r && err) {
6455
return callback(err);
@@ -69,3 +60,5 @@ export class BulkWriteOperation extends OperationBase<BulkWriteOptions, BulkWrit
6960
});
7061
}
7162
}
63+
64+
defineAspects(BulkWriteOperation, [Aspect.WRITE_OPERATION]);

src/operations/command.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { Aspect, OperationBase, OperationOptions } from './operation';
22
import { ReadConcern } from '../read_concern';
33
import { WriteConcern, WriteConcernOptions } from '../write_concern';
44
import { maxWireVersion, MongoDBNamespace, Callback } from '../utils';
5-
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
5+
import type { ReadPreference } from '../read_preference';
66
import { commandSupportsReadConcern } from '../sessions';
77
import { MongoError } from '../error';
88
import type { Logger } from '../logger';
99
import type { Server } from '../sdam/server';
10-
import { BSONSerializeOptions, Document, resolveBSONOptions } from '../bson';
10+
import type { BSONSerializeOptions, Document } from '../bson';
1111
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
1212
import type { ReadConcernLike } from './../read_concern';
1313

@@ -19,8 +19,6 @@ export interface CommandOperationOptions extends OperationOptions, WriteConcernO
1919
fullResponse?: boolean;
2020
/** Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported) */
2121
readConcern?: ReadConcernLike;
22-
/** The preferred read preference (ReadPreference.primary, ReadPreference.primary_preferred, ReadPreference.secondary, ReadPreference.secondary_preferred, ReadPreference.nearest). */
23-
readPreference?: ReadPreferenceLike;
2422
/** Collation */
2523
collation?: CollationOptions;
2624
maxTimeMS?: number;
@@ -51,7 +49,6 @@ export abstract class CommandOperation<
5149
TResult = Document
5250
> extends OperationBase<T> {
5351
ns: MongoDBNamespace;
54-
readPreference: ReadPreference;
5552
readConcern?: ReadConcern;
5653
writeConcern?: WriteConcern;
5754
explain: boolean;
@@ -73,21 +70,13 @@ export abstract class CommandOperation<
7370
: new MongoDBNamespace('admin', '$cmd');
7471
}
7572

76-
this.readPreference = this.hasAspect(Aspect.WRITE_OPERATION)
77-
? ReadPreference.primary
78-
: ReadPreference.fromOptions(options) ?? ReadPreference.primary;
7973
this.readConcern = ReadConcern.fromOptions(options);
8074
this.writeConcern = WriteConcern.fromOptions(options);
81-
this.bsonOptions = resolveBSONOptions(options);
8275

8376
this.explain = false;
8477
this.fullResponse =
8578
options && typeof options.fullResponse === 'boolean' ? options.fullResponse : false;
8679

87-
// TODO: A lot of our code depends on having the read preference in the options. This should
88-
// go away, but also requires massive test rewrites.
89-
this.options.readPreference = this.readPreference;
90-
9180
// TODO(NODE-2056): make logger another "inheritable" property
9281
if (parent && parent.logger) {
9382
this.logger = parent.logger;

src/operations/common_functions.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { MongoError } from '../error';
2-
import {
3-
applyRetryableWrites,
4-
applyWriteConcern,
5-
decorateWithCollation,
6-
Callback,
7-
getTopology
8-
} from '../utils';
2+
import { applyRetryableWrites, decorateWithCollation, Callback, getTopology } from '../utils';
93
import type { Document } from '../bson';
104
import type { Db } from '../db';
115
import type { ClientSession } from '../sessions';
@@ -136,10 +130,9 @@ export function removeDocuments(
136130
// Create an empty options object if the provided one is null
137131
options = options || {};
138132

139-
// Final options for retryable writes and write concern
133+
// Final options for retryable writes
140134
let finalOptions = Object.assign({}, options);
141135
finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
142-
finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
143136

144137
// If selector is null set empty
145138
if (selector == null) selector = {};
@@ -218,16 +211,9 @@ export function updateDocuments(
218211
if (document == null || typeof document !== 'object')
219212
return callback(new TypeError('document must be a valid JavaScript object'));
220213

221-
// Final options for retryable writes and write concern
214+
// Final options for retryable writes
222215
let finalOptions = Object.assign({}, options);
223216
finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
224-
finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
225-
226-
// Do we return the actual result document
227-
// Either use override on the function, or go back to default on either the collection
228-
// level or db
229-
finalOptions.serializeFunctions =
230-
options.serializeFunctions || coll.bsonOptions.serializeFunctions;
231217

232218
// Execute the operation
233219
const op: Document = { q: selector, u: document };

src/operations/find_and_modify.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
maxWireVersion,
44
applyRetryableWrites,
55
decorateWithCollation,
6-
applyWriteConcern,
76
hasAtomicOperators,
87
Callback
98
} from '../utils';
@@ -107,9 +106,8 @@ export class FindAndModifyOperation extends CommandOperation<FindAndModifyOption
107106
// No check on the documents
108107
options.checkKeys = false;
109108

110-
// Final options for retryable writes and write concern
109+
// Final options for retryable writes
111110
options = applyRetryableWrites(options, coll.s.db);
112-
options = applyWriteConcern(options, { db: coll.s.db, collection: coll }, options);
113111

114112
// Decorate the findAndModify command with the write Concern
115113
if (options.writeConcern) {

src/operations/insert.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { MongoError } from '../error';
22
import { defineAspects, Aspect, OperationBase } from './operation';
33
import { CommandOperation } from './command';
4-
import { applyRetryableWrites, applyWriteConcern, Callback, MongoDBNamespace } from '../utils';
4+
import { applyRetryableWrites, Callback, MongoDBNamespace } from '../utils';
55
import { prepareDocs } from './common_functions';
66
import type { Server } from '../sdam/server';
77
import type { Collection } from '../collection';
@@ -98,10 +98,9 @@ function insertDocuments(
9898
// Ensure we are operating on an array op docs
9999
docs = Array.isArray(docs) ? docs : [docs];
100100

101-
// Final options for retryable writes and write concern
101+
// Final options for retryable writes
102102
let finalOptions = Object.assign({}, options);
103103
finalOptions = applyRetryableWrites(finalOptions, coll.s.db);
104-
finalOptions = applyWriteConcern(finalOptions, { db: coll.s.db, collection: coll }, options);
105104

106105
// If keep going set unordered
107106
if (finalOptions.keepGoing === true) finalOptions.ordered = false;

src/operations/insert_many.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { OperationBase } from './operation';
1+
import { Aspect, defineAspects, OperationBase } from './operation';
22
import { BulkWriteOperation } from './bulk_write';
33
import { MongoError } from '../error';
44
import { prepareDocs } from './common_functions';
55
import type { Callback } from '../utils';
66
import type { Collection } from '../collection';
7-
import { ObjectId, Document, resolveBSONOptions } from '../bson';
7+
import type { ObjectId, Document } from '../bson';
88
import type { BulkWriteResult, BulkWriteOptions } from '../bulk/common';
99
import type { Server } from '../sdam/server';
1010

@@ -30,9 +30,6 @@ export class InsertManyOperation extends OperationBase<BulkWriteOptions, InsertM
3030

3131
this.collection = collection;
3232
this.docs = docs;
33-
34-
// Assign BSON serialize options to OperationBase, preferring options over collection options
35-
this.bsonOptions = resolveBSONOptions(options, collection);
3633
}
3734

3835
execute(server: Server, callback: Callback<InsertManyResult>): void {
@@ -73,3 +70,5 @@ function mapInsertManyResults(docs: Document[], r: BulkWriteResult): InsertManyR
7370

7471
return finalResult;
7572
}
73+
74+
defineAspects(InsertManyOperation, [Aspect.WRITE_OPERATION]);

src/operations/operation.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { ReadPreference } from '../read_preference';
1+
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
22
import type { ClientSession } from '../sessions';
3-
import type { Document, BSONSerializeOptions } from '../bson';
3+
import { Document, BSONSerializeOptions, resolveBSONOptions } from '../bson';
44
import type { MongoDBNamespace, Callback } from '../utils';
55
import type { Server } from '../sdam/server';
66

@@ -24,6 +24,9 @@ export interface OperationOptions extends BSONSerializeOptions {
2424

2525
explain?: boolean;
2626
willRetryWrites?: boolean;
27+
28+
/** The preferred read preference (ReadPreference.primary, ReadPreference.primary_preferred, ReadPreference.secondary, ReadPreference.secondary_preferred, ReadPreference.nearest). */
29+
readPreference?: ReadPreferenceLike;
2730
}
2831

2932
/**
@@ -49,7 +52,16 @@ export abstract class OperationBase<
4952

5053
constructor(options: T = {} as T) {
5154
this.options = Object.assign({}, options);
52-
this.readPreference = ReadPreference.primary;
55+
56+
this.readPreference = this.hasAspect(Aspect.WRITE_OPERATION)
57+
? ReadPreference.primary
58+
: ReadPreference.fromOptions(options) ?? ReadPreference.primary;
59+
// TODO: A lot of our code depends on having the read preference in the options. This should
60+
// go away, but also requires massive test rewrites.
61+
this.options.readPreference = this.readPreference;
62+
63+
// Pull the BSON serialize options from the already-resolved options
64+
this.bsonOptions = resolveBSONOptions(options);
5365
}
5466

5567
abstract execute(server: Server, callback: Callback<TResult>): void;

0 commit comments

Comments
 (0)