Skip to content

Commit b4ec3ed

Browse files
authored
refactor: unify means of applying ignoreUndefined (#2589)
This patch stores BSONSerializationOptions as a new inheritable property on `MongoClient`, `Db`, `Collection`, and `OperationBase`, and makes it accessible via a new `bsonOptions` getter for each class. The inherited options, including `ignoreUndefined` are now passed, when relevant, to the server. NODE-1561
1 parent ba76696 commit b4ec3ed

19 files changed

+266
-128
lines changed

src/bson.ts

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { OperationParent } from './operations/command';
12
// import type * as _BSON from 'bson';
23
// let BSON: typeof _BSON = require('bson');
34
// try {
@@ -69,3 +70,25 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
6970
raw
7071
};
7172
}
73+
74+
/**
75+
* Merge the given BSONSerializeOptions, preferring options over the parent's options, and
76+
* substituting defaults for values not set.
77+
*
78+
* @internal
79+
*/
80+
export function resolveBSONOptions(
81+
options?: BSONSerializeOptions,
82+
parent?: OperationParent
83+
): BSONSerializeOptions {
84+
const parentOptions = parent?.bsonOptions;
85+
return {
86+
raw: options?.raw ?? parentOptions?.raw ?? false,
87+
promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
88+
promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
89+
promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,
90+
ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
91+
serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
92+
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {}
93+
};
94+
}

src/collection.ts

+10-79
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
MongoDBNamespace,
99
Callback
1010
} from './utils';
11-
import { ObjectId, Document, BSONSerializeOptions } from './bson';
11+
import { ObjectId, Document, BSONSerializeOptions, resolveBSONOptions } from './bson';
1212
import { MongoError } from './error';
1313
import { UnorderedBulkOperation } from './bulk/unordered';
1414
import { OrderedBulkOperation } from './bulk/ordered';
@@ -127,13 +127,8 @@ export interface CollectionPrivate {
127127
options: any;
128128
namespace: MongoDBNamespace;
129129
readPreference?: ReadPreference;
130+
bsonOptions: BSONSerializeOptions;
130131
slaveOk?: boolean;
131-
serializeFunctions?: boolean;
132-
raw?: boolean;
133-
promoteLongs?: boolean;
134-
promoteValues?: boolean;
135-
promoteBuffers?: boolean;
136-
ignoreUndefined?: boolean;
137132
collectionHint?: Hint;
138133
readConcern?: ReadConcern;
139134
writeConcern?: WriteConcern;
@@ -189,30 +184,10 @@ export class Collection implements OperationParent {
189184
}
190185
},
191186
readPreference: ReadPreference.fromOptions(options),
187+
bsonOptions: resolveBSONOptions(options, db),
192188
readConcern: ReadConcern.fromOptions(options),
193189
writeConcern: WriteConcern.fromOptions(options),
194-
slaveOk: options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk,
195-
serializeFunctions:
196-
options == null || options.serializeFunctions == null
197-
? db.s.options?.serializeFunctions
198-
: options.serializeFunctions,
199-
raw: options == null || options.raw == null ? db.s.options?.raw : options.raw,
200-
promoteLongs:
201-
options == null || options.promoteLongs == null
202-
? db.s.options?.promoteLongs
203-
: options.promoteLongs,
204-
promoteValues:
205-
options == null || options.promoteValues == null
206-
? db.s.options?.promoteValues
207-
: options.promoteValues,
208-
promoteBuffers:
209-
options == null || options.promoteBuffers == null
210-
? db.s.options?.promoteBuffers
211-
: options.promoteBuffers,
212-
ignoreUndefined:
213-
options == null || options.ignoreUndefined == null
214-
? db.s.options?.ignoreUndefined
215-
: options.ignoreUndefined
190+
slaveOk: options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk
216191
};
217192
}
218193

@@ -261,6 +236,10 @@ export class Collection implements OperationParent {
261236
return this.s.readPreference;
262237
}
263238

239+
get bsonOptions(): BSONSerializeOptions {
240+
return this.s.bsonOptions;
241+
}
242+
264243
/**
265244
* The current writeConcern of the collection. If not explicitly defined for
266245
* this collection, will be inherited from the parent DB
@@ -302,12 +281,6 @@ export class Collection implements OperationParent {
302281
if (typeof options === 'function') (callback = options), (options = {});
303282
options = options || {};
304283

305-
// Add ignoreUndefined
306-
if (this.s.options.ignoreUndefined) {
307-
options = Object.assign({}, options);
308-
options.ignoreUndefined = this.s.options.ignoreUndefined;
309-
}
310-
311284
return executeOperation(this.s.topology, new InsertOneOperation(this, doc, options), callback);
312285
}
313286

@@ -429,12 +402,6 @@ export class Collection implements OperationParent {
429402
if (typeof options === 'function') (callback = options), (options = {});
430403
options = Object.assign({}, options);
431404

432-
// Add ignoreUndefined
433-
if (this.s.options.ignoreUndefined) {
434-
options = Object.assign({}, options);
435-
options.ignoreUndefined = this.s.options.ignoreUndefined;
436-
}
437-
438405
return executeOperation(
439406
this.s.topology,
440407
new UpdateOneOperation(this, filter, update, options),
@@ -472,12 +439,6 @@ export class Collection implements OperationParent {
472439
if (typeof options === 'function') (callback = options), (options = {});
473440
options = Object.assign({}, options);
474441

475-
// Add ignoreUndefined
476-
if (this.s.options.ignoreUndefined) {
477-
options = Object.assign({}, options);
478-
options.ignoreUndefined = this.s.options.ignoreUndefined;
479-
}
480-
481442
return executeOperation(
482443
this.s.topology,
483444
new ReplaceOneOperation(this, filter, replacement, options),
@@ -511,12 +472,6 @@ export class Collection implements OperationParent {
511472
if (typeof options === 'function') (callback = options), (options = {});
512473
options = Object.assign({}, options);
513474

514-
// Add ignoreUndefined
515-
if (this.s.options.ignoreUndefined) {
516-
options = Object.assign({}, options);
517-
options.ignoreUndefined = this.s.options.ignoreUndefined;
518-
}
519-
520475
return executeOperation(
521476
this.s.topology,
522477
new UpdateManyOperation(this, filter, update, options),
@@ -543,12 +498,6 @@ export class Collection implements OperationParent {
543498
if (typeof options === 'function') (callback = options), (options = {});
544499
options = Object.assign({}, options);
545500

546-
// Add ignoreUndefined
547-
if (this.s.options.ignoreUndefined) {
548-
options = Object.assign({}, options);
549-
options.ignoreUndefined = this.s.options.ignoreUndefined;
550-
}
551-
552501
return executeOperation(
553502
this.s.topology,
554503
new DeleteOneOperation(this, filter, options),
@@ -587,12 +536,6 @@ export class Collection implements OperationParent {
587536

588537
options = Object.assign({}, options);
589538

590-
// Add ignoreUndefined
591-
if (this.s.options.ignoreUndefined) {
592-
options = Object.assign({}, options);
593-
options.ignoreUndefined = this.s.options.ignoreUndefined;
594-
}
595-
596539
return executeOperation(
597540
this.s.topology,
598541
new DeleteManyOperation(this, filter, options),
@@ -1345,7 +1288,7 @@ export class Collection implements OperationParent {
13451288
options = options || {};
13461289
// Give function's options precedence over session options.
13471290
if (options.ignoreUndefined == null) {
1348-
options.ignoreUndefined = this.s.options.ignoreUndefined;
1291+
options.ignoreUndefined = this.bsonOptions.ignoreUndefined;
13491292
}
13501293

13511294
return new UnorderedBulkOperation(this, options);
@@ -1356,7 +1299,7 @@ export class Collection implements OperationParent {
13561299
options = options || {};
13571300
// Give function's options precedence over session's options.
13581301
if (options.ignoreUndefined == null) {
1359-
options.ignoreUndefined = this.s.options.ignoreUndefined;
1302+
options.ignoreUndefined = this.bsonOptions.ignoreUndefined;
13601303
}
13611304

13621305
return new OrderedBulkOperation(this, options);
@@ -1415,12 +1358,6 @@ export class Collection implements OperationParent {
14151358
if (typeof options === 'function') (callback = options), (options = {});
14161359
options = options || {};
14171360

1418-
// Add ignoreUndefined
1419-
if (this.s.options.ignoreUndefined) {
1420-
options = Object.assign({}, options);
1421-
options.ignoreUndefined = this.s.options.ignoreUndefined;
1422-
}
1423-
14241361
return this.updateMany(selector, update, options, callback);
14251362
}
14261363

@@ -1440,12 +1377,6 @@ export class Collection implements OperationParent {
14401377
if (typeof options === 'function') (callback = options), (options = {});
14411378
options = options || {};
14421379

1443-
// Add ignoreUndefined
1444-
if (this.s.options.ignoreUndefined) {
1445-
options = Object.assign({}, options);
1446-
options.ignoreUndefined = this.s.options.ignoreUndefined;
1447-
}
1448-
14491380
return this.deleteMany(selector, options, callback);
14501381
}
14511382

src/db.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { deprecate } from 'util';
22
import { emitDeprecatedOptionWarning, Callback } from './utils';
33
import { loadAdmin } from './dynamic_loaders';
44
import { AggregationCursor, CommandCursor } from './cursor';
5-
import { ObjectId, Code, Document, BSONSerializeOptions } from './bson';
5+
import { ObjectId, Code, Document, BSONSerializeOptions, resolveBSONOptions } from './bson';
66
import { ReadPreference, ReadPreferenceLike } from './read_preference';
77
import { MongoError } from './error';
88
import { Collection, CollectionOptions } from './collection';
@@ -94,6 +94,7 @@ export interface DbPrivate {
9494
readPreference?: ReadPreference;
9595
pkFactory: PkFactory;
9696
readConcern?: ReadConcern;
97+
bsonOptions: BSONSerializeOptions;
9798
writeConcern?: WriteConcern;
9899
namespace: MongoDBNamespace;
99100
}
@@ -171,6 +172,8 @@ export class Db implements OperationParent {
171172
logger: new Logger('Db', options),
172173
// Unpack read preference
173174
readPreference: ReadPreference.fromOptions(options),
175+
// Merge bson options TODO: include client bson options, after NODE-2850
176+
bsonOptions: resolveBSONOptions(options),
174177
// Set up the primary key factory or fallback to ObjectId
175178
pkFactory: options?.pkFactory ?? {
176179
createPk() {
@@ -218,6 +221,10 @@ export class Db implements OperationParent {
218221
return this.s.readPreference;
219222
}
220223

224+
get bsonOptions(): BSONSerializeOptions {
225+
return this.s.bsonOptions;
226+
}
227+
221228
// get the write Concern
222229
get writeConcern(): WriteConcern | undefined {
223230
return this.s.writeConcern;
@@ -340,11 +347,6 @@ export class Db implements OperationParent {
340347
// If we have not set a collection level readConcern set the db level one
341348
options.readConcern = ReadConcern.fromOptions(options) ?? this.readConcern;
342349

343-
// Do we have ignoreUndefined set
344-
if (this.s.options?.ignoreUndefined) {
345-
options.ignoreUndefined = this.s.options.ignoreUndefined;
346-
}
347-
348350
// Merge in all needed options and ensure correct writeConcern merging from db level
349351
const finalOptions = mergeOptionsAndWriteConcern(
350352
options,

src/mongo_client.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { connect, validOptions } from './operations/connect';
1010
import { PromiseProvider } from './promise_provider';
1111
import { Logger } from './logger';
1212
import { ReadConcern, ReadConcernLevelLike, ReadConcernLike } from './read_concern';
13-
import type { BSONSerializeOptions, Document } from './bson';
13+
import { BSONSerializeOptions, Document, resolveBSONOptions } from './bson';
1414
import type { AutoEncryptionOptions } from './deps';
1515
import type { CompressorName } from './cmap/wire_protocol/compression';
1616
import type { AuthMechanism } from './cmap/auth/defaultAuthProviders';
@@ -222,6 +222,7 @@ export interface MongoClientPrivate {
222222
readConcern?: ReadConcern;
223223
writeConcern?: WriteConcern;
224224
readPreference: ReadPreference;
225+
bsonOptions: BSONSerializeOptions;
225226
namespace: MongoDBNamespace;
226227
logger: Logger;
227228
}
@@ -287,6 +288,7 @@ export class MongoClient extends EventEmitter implements OperationParent {
287288
readConcern: ReadConcern.fromOptions(options),
288289
writeConcern: WriteConcern.fromOptions(options),
289290
readPreference: ReadPreference.fromOptions(options) || ReadPreference.primary,
291+
bsonOptions: resolveBSONOptions(options),
290292
namespace: new MongoDBNamespace('admin'),
291293
logger: options?.logger ?? new Logger('MongoClient')
292294
};
@@ -304,6 +306,10 @@ export class MongoClient extends EventEmitter implements OperationParent {
304306
return this.s.readPreference;
305307
}
306308

309+
get bsonOptions(): BSONSerializeOptions {
310+
return this.s.bsonOptions;
311+
}
312+
307313
get logger(): Logger {
308314
return this.s.logger;
309315
}

src/operations/bulk_write.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { applyRetryableWrites, applyWriteConcern, Callback } from '../utils';
22
import { OperationBase } from './operation';
3+
import { resolveBSONOptions } from '../bson';
34
import { WriteConcern } from '../write_concern';
45
import type { Collection } from '../collection';
56
import type {
@@ -24,18 +25,15 @@ export class BulkWriteOperation extends OperationBase<BulkWriteOptions, BulkWrit
2425

2526
this.collection = collection;
2627
this.operations = operations;
28+
29+
// Assign BSON serialize options to OperationBase, preferring options over collection options
30+
this.bsonOptions = resolveBSONOptions(options, collection);
2731
}
2832

2933
execute(server: Server, callback: Callback<BulkWriteResult>): void {
3034
const coll = this.collection;
3135
const operations = this.operations;
32-
let options = this.options;
33-
34-
// Add ignoreUndefined
35-
if (coll.s.options.ignoreUndefined) {
36-
options = Object.assign({}, options);
37-
options.ignoreUndefined = coll.s.options.ignoreUndefined;
38-
}
36+
const options = { ...this.options, ...this.bsonOptions };
3937

4038
// Create the bulk operation
4139
const bulk: BulkOperationBase =

src/operations/command.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { commandSupportsReadConcern } from '../sessions';
77
import { MongoError } from '../error';
88
import type { Logger } from '../logger';
99
import type { Server } from '../sdam/server';
10-
import type { Document } from '../bson';
10+
import { BSONSerializeOptions, Document, resolveBSONOptions } from '../bson';
1111
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
1212
import type { ReadConcernLike } from './../read_concern';
1313

@@ -42,6 +42,7 @@ export interface OperationParent {
4242
writeConcern?: WriteConcern;
4343
readPreference?: ReadPreference;
4444
logger?: Logger;
45+
bsonOptions?: BSONSerializeOptions;
4546
}
4647

4748
/** @internal */
@@ -90,6 +91,9 @@ export abstract class CommandOperation<
9091
if (parent && parent.logger) {
9192
this.logger = parent.logger;
9293
}
94+
95+
// Assign BSON serialize options to OperationBase, preferring options over parent options.
96+
this.bsonOptions = resolveBSONOptions(options, parent);
9397
}
9498

9599
abstract execute(server: Server, callback: Callback<TResult>): void;
@@ -98,7 +102,7 @@ export abstract class CommandOperation<
98102
// TODO: consider making this a non-enumerable property
99103
this.server = server;
100104

101-
const options = this.options;
105+
const options = { ...this.options, ...this.bsonOptions };
102106
const serverWireVersion = maxWireVersion(server);
103107
const inTransaction = this.session && this.session.inTransaction();
104108

src/operations/common_functions.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ export function updateDocuments(
221221
// Do we return the actual result document
222222
// Either use override on the function, or go back to default on either the collection
223223
// level or db
224-
finalOptions.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
224+
finalOptions.serializeFunctions =
225+
options.serializeFunctions || coll.bsonOptions.serializeFunctions;
225226

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

src/operations/delete.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class DeleteOneOperation extends CommandOperation<DeleteOptions, DeleteRe
6565
execute(server: Server, callback: Callback<DeleteResult>): void {
6666
const coll = this.collection;
6767
const filter = this.filter;
68-
const options = this.options;
68+
const options = { ...this.options, ...this.bsonOptions };
6969

7070
options.single = true;
7171
removeDocuments(server, coll, filter, options, (err, r) => {
@@ -99,7 +99,7 @@ export class DeleteManyOperation extends CommandOperation<DeleteOptions, DeleteR
9999
execute(server: Server, callback: Callback<DeleteResult>): void {
100100
const coll = this.collection;
101101
const filter = this.filter;
102-
const options = this.options;
102+
const options = { ...this.options, ...this.bsonOptions };
103103

104104
// a user can pass `single: true` in to `deleteMany` to remove a single document, theoretically
105105
if (typeof options.single !== 'boolean') {

0 commit comments

Comments
 (0)