Skip to content

Commit d29eb8c

Browse files
authored
fix(NODE-4496): counter values incorrecly compared when instance of Long (#3342)
1 parent 417655a commit d29eb8c

File tree

9 files changed

+245
-90
lines changed

9 files changed

+245
-90
lines changed

src/bulk/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ export class WriteError {
441441

442442
/** Converts the number to a Long or returns it. */
443443
function longOrConvert(value: number | Long | Timestamp): Long | Timestamp {
444+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
444445
return typeof value === 'number' ? Long.fromNumber(value) : value;
445446
}
446447

src/cursor/abstract_cursor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ export abstract class AbstractCursor<
654654
this[kServer] = state.server;
655655

656656
if (response.cursor) {
657+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
657658
this[kId] =
658659
typeof response.cursor.id === 'number'
659660
? Long.fromNumber(response.cursor.id)

src/sdam/monitor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ function makeTopologyVersion(tv: TopologyVersion) {
374374
return {
375375
processId: tv.processId,
376376
// tests mock counter as just number, but in a real situation counter should always be a Long
377+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
377378
counter: Long.isLong(tv.counter) ? tv.counter : Long.fromNumber(tv.counter)
378379
};
379380
}

src/sdam/server_description.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Document, Long, ObjectId } from '../bson';
22
import type { MongoError } from '../error';
3-
import { arrayStrictEqual, errorStrictEqual, HostAddress, now } from '../utils';
3+
import { arrayStrictEqual, compareObjectId, errorStrictEqual, HostAddress, now } from '../utils';
44
import type { ClusterTime } from './common';
55
import { ServerType } from './common';
66

@@ -105,6 +105,7 @@ export class ServerDescription {
105105
if (options?.topologyVersion) {
106106
this.topologyVersion = options.topologyVersion;
107107
} else if (hello?.topologyVersion) {
108+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
108109
this.topologyVersion = hello.topologyVersion;
109110
}
110111

@@ -179,15 +180,16 @@ export class ServerDescription {
179180
* Determines if another `ServerDescription` is equal to this one per the rules defined
180181
* in the {@link https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription|SDAM spec}
181182
*/
182-
equals(other: ServerDescription): boolean {
183+
equals(other?: ServerDescription | null): boolean {
184+
// TODO(NODE-4510): Check ServerDescription equality logic for nullish topologyVersion meaning "greater than"
183185
const topologyVersionsEqual =
184-
this.topologyVersion === other.topologyVersion ||
185-
compareTopologyVersion(this.topologyVersion, other.topologyVersion) === 0;
186+
this.topologyVersion === other?.topologyVersion ||
187+
compareTopologyVersion(this.topologyVersion, other?.topologyVersion) === 0;
186188

187-
const electionIdsEqual: boolean =
188-
this.electionId && other.electionId
189-
? other.electionId && this.electionId.equals(other.electionId)
190-
: this.electionId === other.electionId;
189+
const electionIdsEqual =
190+
this.electionId != null && other?.electionId != null
191+
? compareObjectId(this.electionId, other.electionId) === 0
192+
: this.electionId === other?.electionId;
191193

192194
return (
193195
other != null &&
@@ -254,19 +256,37 @@ function tagsStrictEqual(tags: TagSet, tags2: TagSet): boolean {
254256
/**
255257
* Compares two topology versions.
256258
*
257-
* @returns A negative number if `lhs` is older than `rhs`; positive if `lhs` is newer than `rhs`; 0 if they are equivalent.
259+
* 1. If the response topologyVersion is unset or the ServerDescription's
260+
* topologyVersion is null, the client MUST assume the response is more recent.
261+
* 1. If the response's topologyVersion.processId is not equal to the
262+
* ServerDescription's, the client MUST assume the response is more recent.
263+
* 1. If the response's topologyVersion.processId is equal to the
264+
* ServerDescription's, the client MUST use the counter field to determine
265+
* which topologyVersion is more recent.
266+
*
267+
* ```ts
268+
* currentTv < newTv === -1
269+
* currentTv === newTv === 0
270+
* currentTv > newTv === 1
271+
* ```
258272
*/
259-
export function compareTopologyVersion(lhs?: TopologyVersion, rhs?: TopologyVersion): number {
260-
if (lhs == null || rhs == null) {
273+
export function compareTopologyVersion(
274+
currentTv?: TopologyVersion,
275+
newTv?: TopologyVersion
276+
): 0 | -1 | 1 {
277+
if (currentTv == null || newTv == null) {
261278
return -1;
262279
}
263280

264-
if (lhs.processId.equals(rhs.processId)) {
265-
// tests mock counter as just number, but in a real situation counter should always be a Long
266-
const lhsCounter = Long.isLong(lhs.counter) ? lhs.counter : Long.fromNumber(lhs.counter);
267-
const rhsCounter = Long.isLong(rhs.counter) ? lhs.counter : Long.fromNumber(rhs.counter);
268-
return lhsCounter.compare(rhsCounter);
281+
if (!currentTv.processId.equals(newTv.processId)) {
282+
return -1;
269283
}
270284

271-
return -1;
285+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
286+
const currentCounter = Long.isLong(currentTv.counter)
287+
? currentTv.counter
288+
: Long.fromNumber(currentTv.counter);
289+
const newCounter = Long.isLong(newTv.counter) ? newTv.counter : Long.fromNumber(newTv.counter);
290+
291+
return currentCounter.compare(newCounter);
272292
}

src/sdam/topology_description.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { Document, ObjectId } from '../bson';
1+
import type { ObjectId } from '../bson';
22
import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants';
33
import { MongoError, MongoRuntimeError } from '../error';
4-
import { shuffle } from '../utils';
4+
import { compareObjectId, shuffle } from '../utils';
55
import { ServerType, TopologyType } from './common';
66
import { ServerDescription } from './server_description';
77
import type { SrvPollingEvent } from './srv_polling';
@@ -363,27 +363,6 @@ function topologyTypeForServerType(serverType: ServerType): TopologyType {
363363
}
364364
}
365365

366-
// TODO: improve these docs when ObjectId is properly typed
367-
function compareObjectId(oid1: Document, oid2: Document): number {
368-
if (oid1 == null) {
369-
return -1;
370-
}
371-
372-
if (oid2 == null) {
373-
return 1;
374-
}
375-
376-
if (oid1.id instanceof Buffer && oid2.id instanceof Buffer) {
377-
const oid1Buffer = oid1.id;
378-
const oid2Buffer = oid2.id;
379-
return oid1Buffer.compare(oid2Buffer);
380-
}
381-
382-
const oid1String = oid1.toString();
383-
const oid2String = oid2.toString();
384-
return oid1String.localeCompare(oid2String);
385-
}
386-
387366
function updateRsFromPrimary(
388367
serverDescriptions: Map<string, ServerDescription>,
389368
serverDescription: ServerDescription,

src/sessions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,7 @@ export function applySession(
10021002
if (isRetryableWrite || inTxnOrTxnCommand) {
10031003
serverSession.txnNumber += session[kTxnNumberIncrement];
10041004
session[kTxnNumberIncrement] = 0;
1005+
// TODO(NODE-2674): Preserve int64 sent from MongoDB
10051006
command.txnNumber = Long.fromNumber(serverSession.txnNumber);
10061007
}
10071008

src/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,3 +1387,25 @@ export function getMongoDBClientEncryption(): {
13871387

13881388
return mongodbClientEncryption;
13891389
}
1390+
1391+
/**
1392+
* Compare objectIds. `null` is always less
1393+
* - `+1 = oid1 is greater than oid2`
1394+
* - `-1 = oid1 is less than oid2`
1395+
* - `+0 = oid1 is equal oid2`
1396+
*/
1397+
export function compareObjectId(oid1?: ObjectId, oid2?: ObjectId): 0 | 1 | -1 {
1398+
if (oid1 == null && oid2 == null) {
1399+
return 0;
1400+
}
1401+
1402+
if (oid1 == null) {
1403+
return -1;
1404+
}
1405+
1406+
if (oid2 == null) {
1407+
return 1;
1408+
}
1409+
1410+
return oid1.id.compare(oid2.id);
1411+
}

test/unit/sdam/server_description.test.js

Lines changed: 0 additions & 50 deletions
This file was deleted.

0 commit comments

Comments
 (0)