Skip to content

Commit 735f7aa

Browse files
feat(NODE-3449): Add serverConnectionId to Command Monitoring Spec
Co-authored-by: Durran Jordan <[email protected]>
1 parent a3c0298 commit 735f7aa

File tree

6 files changed

+153
-13
lines changed

6 files changed

+153
-13
lines changed

Diff for: src/cmap/command_monitoring_events.ts

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Document, ObjectId } from '../bson';
1+
import { type Document, type ObjectId } from '../bson';
22
import {
33
COMMAND_FAILED,
44
COMMAND_STARTED,
@@ -22,7 +22,14 @@ export class CommandStartedEvent {
2222
commandName: string;
2323
command: Document;
2424
address: string;
25+
/** Driver generated connection id */
2526
connectionId?: string | number;
27+
/**
28+
* Server generated connection id
29+
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId"
30+
* from the server on 4.2+.
31+
*/
32+
serverConnectionId: bigint | null;
2633
serviceId?: ObjectId;
2734
/** @internal */
2835
name = COMMAND_STARTED;
@@ -34,7 +41,11 @@ export class CommandStartedEvent {
3441
* @param pool - the pool that originated the command
3542
* @param command - the command
3643
*/
37-
constructor(connection: Connection, command: WriteProtocolMessageType) {
44+
constructor(
45+
connection: Connection,
46+
command: WriteProtocolMessageType,
47+
serverConnectionId: bigint | null
48+
) {
3849
const cmd = extractCommand(command);
3950
const commandName = extractCommandName(cmd);
4051
const { address, connectionId, serviceId } = extractConnectionDetails(connection);
@@ -52,6 +63,7 @@ export class CommandStartedEvent {
5263
this.databaseName = command.databaseName;
5364
this.commandName = commandName;
5465
this.command = maybeRedact(commandName, cmd, cmd);
66+
this.serverConnectionId = serverConnectionId;
5567
}
5668

5769
/* @internal */
@@ -67,7 +79,13 @@ export class CommandStartedEvent {
6779
*/
6880
export class CommandSucceededEvent {
6981
address: string;
82+
/** Driver generated connection id */
7083
connectionId?: string | number;
84+
/**
85+
* Server generated connection id
86+
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server on 4.2+.
87+
*/
88+
serverConnectionId: bigint | null;
7189
requestId: number;
7290
duration: number;
7391
commandName: string;
@@ -89,7 +107,8 @@ export class CommandSucceededEvent {
89107
connection: Connection,
90108
command: WriteProtocolMessageType,
91109
reply: Document | undefined,
92-
started: number
110+
started: number,
111+
serverConnectionId: bigint | null
93112
) {
94113
const cmd = extractCommand(command);
95114
const commandName = extractCommandName(cmd);
@@ -102,6 +121,7 @@ export class CommandSucceededEvent {
102121
this.commandName = commandName;
103122
this.duration = calculateDurationInMs(started);
104123
this.reply = maybeRedact(commandName, cmd, extractReply(command, reply));
124+
this.serverConnectionId = serverConnectionId;
105125
}
106126

107127
/* @internal */
@@ -117,7 +137,13 @@ export class CommandSucceededEvent {
117137
*/
118138
export class CommandFailedEvent {
119139
address: string;
140+
/** Driver generated connection id */
120141
connectionId?: string | number;
142+
/**
143+
* Server generated connection id
144+
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server on 4.2+.
145+
*/
146+
serverConnectionId: bigint | null;
121147
requestId: number;
122148
duration: number;
123149
commandName: string;
@@ -139,7 +165,8 @@ export class CommandFailedEvent {
139165
connection: Connection,
140166
command: WriteProtocolMessageType,
141167
error: Error | Document,
142-
started: number
168+
started: number,
169+
serverConnectionId: bigint | null
143170
) {
144171
const cmd = extractCommand(command);
145172
const commandName = extractCommandName(cmd);
@@ -153,6 +180,7 @@ export class CommandFailedEvent {
153180
this.commandName = commandName;
154181
this.duration = calculateDurationInMs(started);
155182
this.failure = maybeRedact(commandName, cmd, error) as Error;
183+
this.serverConnectionId = serverConnectionId;
156184
}
157185

158186
/* @internal */

Diff for: src/cmap/connection.ts

+41-8
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,10 @@ function write(
717717

718718
// if command monitoring is enabled we need to modify the callback here
719719
if (conn.monitorCommands) {
720-
conn.emit(Connection.COMMAND_STARTED, new CommandStartedEvent(conn, command));
720+
conn.emit(
721+
Connection.COMMAND_STARTED,
722+
new CommandStartedEvent(conn, command, conn[kDescription].serverConnectionId)
723+
);
721724

722725
operationDescription.started = now();
723726
operationDescription.cb = (err, reply) => {
@@ -727,18 +730,36 @@ function write(
727730
if (err && reply?.ok !== 1) {
728731
conn.emit(
729732
Connection.COMMAND_FAILED,
730-
new CommandFailedEvent(conn, command, err, operationDescription.started)
733+
new CommandFailedEvent(
734+
conn,
735+
command,
736+
err,
737+
operationDescription.started,
738+
conn[kDescription].serverConnectionId
739+
)
731740
);
732741
} else {
733742
if (reply && (reply.ok === 0 || reply.$err)) {
734743
conn.emit(
735744
Connection.COMMAND_FAILED,
736-
new CommandFailedEvent(conn, command, reply, operationDescription.started)
745+
new CommandFailedEvent(
746+
conn,
747+
command,
748+
reply,
749+
operationDescription.started,
750+
conn[kDescription].serverConnectionId
751+
)
737752
);
738753
} else {
739754
conn.emit(
740755
Connection.COMMAND_SUCCEEDED,
741-
new CommandSucceededEvent(conn, command, reply, operationDescription.started)
756+
new CommandSucceededEvent(
757+
conn,
758+
command,
759+
reply,
760+
operationDescription.started,
761+
conn[kDescription].serverConnectionId
762+
)
742763
);
743764
}
744765
}
@@ -1098,7 +1119,11 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
10981119
started = now();
10991120
this.emit(
11001121
ModernConnection.COMMAND_STARTED,
1101-
new CommandStartedEvent(this as unknown as Connection, message)
1122+
new CommandStartedEvent(
1123+
this as unknown as Connection,
1124+
message,
1125+
this[kDescription].serverConnectionId
1126+
)
11021127
);
11031128
}
11041129

@@ -1124,7 +1149,8 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
11241149
this as unknown as Connection,
11251150
message,
11261151
options.noResponse ? undefined : document,
1127-
started
1152+
started,
1153+
this[kDescription].serverConnectionId
11281154
)
11291155
);
11301156
}
@@ -1141,12 +1167,19 @@ export class ModernConnection extends TypedEventEmitter<ConnectionEvents> {
11411167
this as unknown as Connection,
11421168
message,
11431169
options.noResponse ? undefined : document,
1144-
started
1170+
started,
1171+
this[kDescription].serverConnectionId
11451172
)
11461173
)
11471174
: this.emit(
11481175
ModernConnection.COMMAND_FAILED,
1149-
new CommandFailedEvent(this as unknown as Connection, message, error, started)
1176+
new CommandFailedEvent(
1177+
this as unknown as Connection,
1178+
message,
1179+
error,
1180+
started,
1181+
this[kDescription].serverConnectionId
1182+
)
11501183
);
11511184
}
11521185
throw error;

Diff for: src/cmap/stream_description.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Document } from '../bson';
1+
import { type Document, type Double, Long } from '../bson';
22
import { ServerType } from '../sdam/common';
33
import { parseServerType } from '../sdam/server_description';
44
import type { CompressorName } from './wire_protocol/compression';
@@ -36,6 +36,7 @@ export class StreamDescription {
3636
__nodejs_mock_server__?: boolean;
3737

3838
zlibCompressionLevel?: number;
39+
serverConnectionId: bigint | null;
3940

4041
constructor(address: string, options?: StreamDescriptionOptions) {
4142
this.address = address;
@@ -51,13 +52,19 @@ export class StreamDescription {
5152
options && options.compressors && Array.isArray(options.compressors)
5253
? options.compressors
5354
: [];
55+
this.serverConnectionId = null;
5456
}
5557

5658
receiveResponse(response: Document | null): void {
5759
if (response == null) {
5860
return;
5961
}
6062
this.type = parseServerType(response);
63+
if ('connectionId' in response) {
64+
this.serverConnectionId = this.parseServerConnectionID(response.connectionId);
65+
} else {
66+
this.serverConnectionId = null;
67+
}
6168
for (const field of RESPONSE_FIELDS) {
6269
if (response[field] != null) {
6370
this[field] = response[field];
@@ -73,4 +80,14 @@ export class StreamDescription {
7380
this.compressor = this.compressors.filter(c => response.compression?.includes(c))[0];
7481
}
7582
}
83+
84+
/* @internal */
85+
parseServerConnectionID(serverConnectionId: number | Double | bigint | Long): bigint {
86+
// Connection ids are always integral, so it's safe to coerce doubles as well as
87+
// any integral type.
88+
return Long.isLong(serverConnectionId)
89+
? serverConnectionId.toBigInt()
90+
: // @ts-expect-error: Doubles are coercible to number
91+
BigInt(serverConnectionId);
92+
}
7693
}

Diff for: test/tools/unified-spec-runner/match.ts

+15
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,11 @@ function compareEvents(
493493
expect.fail(`expected ${path} to be instanceof CommandStartedEvent`);
494494
}
495495
compareCommandStartedEvents(actualEvent, expectedEvent.commandStartedEvent, entities, path);
496+
if (expectedEvent.commandStartedEvent.hasServerConnectionId) {
497+
expect(actualEvent).property('serverConnectionId').to.be.a('bigint');
498+
} else if (expectedEvent.commandStartedEvent.hasServerConnectionId === false) {
499+
expect(actualEvent).property('serverConnectionId').to.be.null;
500+
}
496501
} else if (expectedEvent.commandSucceededEvent) {
497502
const path = `${rootPrefix}.commandSucceededEvent`;
498503
if (!(actualEvent instanceof CommandSucceededEvent)) {
@@ -504,12 +509,22 @@ function compareEvents(
504509
entities,
505510
path
506511
);
512+
if (expectedEvent.commandSucceededEvent.hasServerConnectionId) {
513+
expect(actualEvent).property('serverConnectionId').to.be.a('bigint');
514+
} else if (expectedEvent.commandSucceededEvent.hasServerConnectionId === false) {
515+
expect(actualEvent).property('serverConnectionId').to.be.null;
516+
}
507517
} else if (expectedEvent.commandFailedEvent) {
508518
const path = `${rootPrefix}.commandFailedEvent`;
509519
if (!(actualEvent instanceof CommandFailedEvent)) {
510520
expect.fail(`expected ${path} to be instanceof CommandFailedEvent`);
511521
}
512522
compareCommandFailedEvents(actualEvent, expectedEvent.commandFailedEvent, entities, path);
523+
if (expectedEvent.commandFailedEvent.hasServerConnectionId) {
524+
expect(actualEvent).property('serverConnectionId').to.be.a('bigint');
525+
} else if (expectedEvent.commandFailedEvent.hasServerConnectionId === false) {
526+
expect(actualEvent).property('serverConnectionId').to.be.null;
527+
}
513528
} else if (expectedEvent.connectionClosedEvent) {
514529
expect(actualEvent).to.be.instanceOf(ConnectionClosedEvent);
515530
if (expectedEvent.connectionClosedEvent.hasServiceId) {

Diff for: test/tools/unified-spec-runner/schema.ts

+3
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,16 @@ export interface ExpectedCommandEvent {
282282
command?: Document;
283283
commandName?: string;
284284
databaseName?: string;
285+
hasServerConnectionId?: boolean;
285286
};
286287
commandSucceededEvent?: {
287288
reply?: Document;
288289
commandName?: string;
290+
hasServerConnectionId?: boolean;
289291
};
290292
commandFailedEvent?: {
291293
commandName?: string;
294+
hasServerConnectionId?: boolean;
292295
};
293296
}
294297
export interface ExpectedCmapEvent {

Diff for: test/unit/cmap/stream_description.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const { Double, Long } = require('bson');
34
const { StreamDescription } = require('../../mongodb');
45
const { expect } = require('chai');
56

@@ -64,4 +65,47 @@ describe('StreamDescription - unit/cmap', function () {
6465
});
6566
});
6667
});
68+
69+
describe('serverConnectionId', function () {
70+
context('when serverConnectionId is in hello response', function () {
71+
// eslint-disable-next-line no-undef
72+
const expectedServerConnectionId = BigInt(2);
73+
context('when serverConnectionId of type bigint', function () {
74+
it('should save serverConnectionID as a bigint on stream description', function () {
75+
const description = new StreamDescription('a:27017', {});
76+
// eslint-disable-next-line no-undef
77+
description.receiveResponse({ connectionId: BigInt(2) });
78+
expect(description.serverConnectionId).to.equal(expectedServerConnectionId);
79+
});
80+
});
81+
context('when serverConnectionId is of type BSON Double', function () {
82+
it('should save serverConnectionID as a bigint on stream description', function () {
83+
const description = new StreamDescription('a:27017', {});
84+
description.receiveResponse({ connectionId: new Double(2) });
85+
expect(description.serverConnectionId).to.equal(expectedServerConnectionId);
86+
});
87+
});
88+
context('when serverConnectionId is of type number', function () {
89+
it('should save serverConnectionID as a bigint on stream description', function () {
90+
const description = new StreamDescription('a:27017', {});
91+
description.receiveResponse({ connectionId: 2 });
92+
expect(description.serverConnectionId).to.equal(expectedServerConnectionId);
93+
});
94+
});
95+
context('when serverConnectionId is of type BSON Long', function () {
96+
it('should parse serverConnectionId properly', function () {
97+
const description = new StreamDescription('a:27017', {});
98+
description.receiveResponse({ connectionId: new Long(2) });
99+
expect(description.serverConnectionId).to.equal(expectedServerConnectionId);
100+
});
101+
});
102+
});
103+
context('when serverConnectionId is not in hello response', function () {
104+
it('should not throw an error and keep serverConnectionId undefined on stream description', function () {
105+
const description = new StreamDescription('a:27017', {});
106+
description.receiveResponse({});
107+
expect(description.serverConnectionId).to.not.exist;
108+
});
109+
});
110+
});
67111
});

0 commit comments

Comments
 (0)