Skip to content

Commit 15d8b61

Browse files
committed
chore: ensure command monitoring receives the same object instance
1 parent 072b200 commit 15d8b61

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

src/cmap/connection.ts

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,21 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
479479
);
480480
}
481481

482-
let document;
482+
// If `documentsReturnedIn` not set or raw is not enabled, use input bson options
483+
// Otherwise, support raw flag. Raw only works for cursors that hardcode firstBatch/nextBatch fields
484+
const bsonOptions =
485+
options.documentsReturnedIn == null || !options.raw
486+
? options
487+
: {
488+
...options,
489+
raw: false,
490+
fieldsAsRaw: { [options.documentsReturnedIn]: true }
491+
};
492+
493+
/** MongoDBResponse instance or subclass */
494+
let document: MongoDBResponse | undefined = undefined;
495+
/** Cached result of a toObject call */
496+
let object: Document | undefined = undefined;
483497
try {
484498
this.throwIfAborted();
485499
for await (document of this.sendWire(message, options, responseType)) {
@@ -493,15 +507,12 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
493507
}
494508

495509
if (document.has('writeConcernError')) {
496-
const objectWithWriteConcernError = document.toObject(options);
497-
throw new MongoWriteConcernError(
498-
objectWithWriteConcernError.writeConcernError,
499-
objectWithWriteConcernError
500-
);
510+
object ??= document.toObject(bsonOptions);
511+
throw new MongoWriteConcernError(object.writeConcernError, object);
501512
}
502513

503514
if (document.isError) {
504-
throw new MongoServerError(document.toObject(options));
515+
throw new MongoServerError((object ??= document.toObject(bsonOptions)));
505516
}
506517

507518
if (this.shouldEmitAndLogCommand) {
@@ -513,25 +524,15 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
513524
new CommandSucceededEvent(
514525
this,
515526
message,
516-
options.noResponse ? undefined : document.toObject(options),
527+
options.noResponse ? undefined : (object ??= document.toObject(bsonOptions)),
517528
started,
518529
this.description.serverConnectionId
519530
)
520531
);
521532
}
522533

523534
if (responseType == null) {
524-
// If `documentsReturnedIn` not set or raw is not enabled, use input bson options
525-
// Otherwise, support raw flag. Raw only works for cursors that hardcode firstBatch/nextBatch fields
526-
const bsonOptions =
527-
options.documentsReturnedIn == null || !options.raw
528-
? options
529-
: {
530-
...options,
531-
raw: false,
532-
fieldsAsRaw: { [options.documentsReturnedIn]: true }
533-
};
534-
yield document.toObject(bsonOptions);
535+
yield (object ??= document.toObject(bsonOptions));
535536
} else {
536537
yield document;
537538
}
@@ -549,7 +550,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
549550
new CommandSucceededEvent(
550551
this,
551552
message,
552-
options.noResponse ? undefined : document?.toObject(options),
553+
options.noResponse ? undefined : (object ??= document?.toObject(bsonOptions)),
553554
started,
554555
this.description.serverConnectionId
555556
)

test/integration/connection-monitoring-and-pooling/connection.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
makeClientMetadata,
1515
MongoClient,
1616
MongoClientAuthProviders,
17+
MongoDBResponse,
1718
MongoServerError,
1819
ns,
1920
ServerHeartbeatStartedEvent,
@@ -101,6 +102,48 @@ describe('Connection', function () {
101102
}
102103
}
103104
});
105+
106+
afterEach(() => sinon.restore());
107+
108+
it('command monitoring event do not deserialize more than once', {
109+
metadata: { requires: { apiVersion: false, topology: '!load-balanced' } },
110+
test: async function () {
111+
const connectOptions: ConnectionOptions = {
112+
...commonConnectOptions,
113+
connectionType: Connection,
114+
...this.configuration.options,
115+
monitorCommands: true,
116+
metadata: makeClientMetadata({ driverInfo: {} }),
117+
extendedMetadata: addContainerMetadata(makeClientMetadata({ driverInfo: {} }))
118+
};
119+
120+
let conn;
121+
try {
122+
conn = await connect(connectOptions);
123+
124+
const toObjectSpy = sinon.spy(MongoDBResponse.prototype, 'toObject');
125+
126+
const events: any[] = [];
127+
conn.on('commandStarted', event => events.push(event));
128+
conn.on('commandSucceeded', event => events.push(event));
129+
conn.on('commandFailed', event => events.push(event));
130+
131+
const hello = await conn.command(ns('admin.$cmd'), { ping: 1 });
132+
expect(toObjectSpy).to.have.been.calledOnce;
133+
expect(hello).to.have.property('ok', 1);
134+
expect(events).to.have.lengthOf(2);
135+
136+
toObjectSpy.resetHistory();
137+
138+
const garbage = await conn.command(ns('admin.$cmd'), { garbage: 1 }).catch(e => e);
139+
expect(toObjectSpy).to.have.been.calledOnce;
140+
expect(garbage).to.have.property('ok', 0);
141+
expect(events).to.have.lengthOf(4);
142+
} finally {
143+
conn?.destroy();
144+
}
145+
}
146+
});
104147
});
105148

106149
describe('Connection - functional', function () {

0 commit comments

Comments
 (0)