Skip to content

Commit 917668c

Browse files
W-A-Jamesnbbeekenbaileympearson
authored
feat(NODE-4877): Add support for useBigInt64 (#3519)
Co-authored-by: Neal Beeken <[email protected]> Co-authored-by: Bailey Pearson <[email protected]>
1 parent 10146a4 commit 917668c

22 files changed

+670
-378
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,4 @@ etc/docs/build
8787
!docs/**/*.png
8888
!docs/**/*.css
8989
!docs/**/*.js
90+
.nvmrc

package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"email": "[email protected]"
2626
},
2727
"dependencies": {
28-
"bson": "^5.0.0",
28+
"bson": "^5.0.1",
2929
"mongodb-connection-string-url": "^2.6.0",
3030
"socks": "^2.7.1"
3131
},

src/bson.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export interface BSONSerializeOptions
3636
| 'allowObjectSmallerThanBufferSize'
3737
| 'index'
3838
| 'validation'
39-
| 'useBigInt64'
4039
> {
4140
/**
4241
* Enabling the raw option will return a [Node.js Buffer](https://nodejs.org/api/buffer.html)
@@ -67,6 +66,7 @@ export interface BSONSerializeOptions
6766
export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSerializeOptions {
6867
const {
6968
fieldsAsRaw,
69+
useBigInt64,
7070
promoteValues,
7171
promoteBuffers,
7272
promoteLongs,
@@ -78,6 +78,7 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
7878
} = options;
7979
return {
8080
fieldsAsRaw,
81+
useBigInt64,
8182
promoteValues,
8283
promoteBuffers,
8384
promoteLongs,
@@ -102,6 +103,7 @@ export function resolveBSONOptions(
102103
const parentOptions = parent?.bsonOptions;
103104
return {
104105
raw: options?.raw ?? parentOptions?.raw ?? false,
106+
useBigInt64: options?.useBigInt64 ?? parentOptions?.useBigInt64 ?? false,
105107
promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
106108
promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
107109
promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,

src/cmap/auth/mongodb_aws.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const AWS_RELATIVE_URI = 'http://169.254.170.2';
2121
const AWS_EC2_URI = 'http://169.254.169.254';
2222
const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
2323
const bsonOptions: BSONSerializeOptions = {
24+
useBigInt64: false,
2425
promoteLongs: true,
2526
promoteValues: true,
2627
promoteBuffers: false,

src/cmap/commands.ts

+10
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ export class Response {
304304
queryFailure?: boolean;
305305
shardConfigStale?: boolean;
306306
awaitCapable?: boolean;
307+
useBigInt64: boolean;
307308
promoteLongs: boolean;
308309
promoteValues: boolean;
309310
promoteBuffers: boolean;
@@ -320,6 +321,7 @@ export class Response {
320321
this.raw = message;
321322
this.data = msgBody;
322323
this.opts = opts ?? {
324+
useBigInt64: false,
323325
promoteLongs: true,
324326
promoteValues: true,
325327
promoteBuffers: false,
@@ -334,6 +336,7 @@ export class Response {
334336
this.fromCompressed = msgHeader.fromCompressed;
335337

336338
// Flag values
339+
this.useBigInt64 = typeof this.opts.useBigInt64 === 'boolean' ? this.opts.useBigInt64 : false;
337340
this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
338341
this.promoteValues =
339342
typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
@@ -354,6 +357,7 @@ export class Response {
354357
// Allow the return of raw documents instead of parsing
355358
const raw = options.raw || false;
356359
const documentsReturnedIn = options.documentsReturnedIn || null;
360+
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
357361
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
358362
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
359363
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
@@ -362,6 +366,7 @@ export class Response {
362366

363367
// Set up the options
364368
const _options: BSONSerializeOptions = {
369+
useBigInt64,
365370
promoteLongs,
366371
promoteValues,
367372
promoteBuffers,
@@ -590,6 +595,7 @@ export class BinMsg {
590595
checksumPresent: boolean;
591596
moreToCome: boolean;
592597
exhaustAllowed: boolean;
598+
useBigInt64: boolean;
593599
promoteLongs: boolean;
594600
promoteValues: boolean;
595601
promoteBuffers: boolean;
@@ -607,6 +613,7 @@ export class BinMsg {
607613
this.raw = message;
608614
this.data = msgBody;
609615
this.opts = opts ?? {
616+
useBigInt64: false,
610617
promoteLongs: true,
611618
promoteValues: true,
612619
promoteBuffers: false,
@@ -625,6 +632,7 @@ export class BinMsg {
625632
this.checksumPresent = (this.responseFlags & OPTS_CHECKSUM_PRESENT) !== 0;
626633
this.moreToCome = (this.responseFlags & OPTS_MORE_TO_COME) !== 0;
627634
this.exhaustAllowed = (this.responseFlags & OPTS_EXHAUST_ALLOWED) !== 0;
635+
this.useBigInt64 = typeof this.opts.useBigInt64 === 'boolean' ? this.opts.useBigInt64 : false;
628636
this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
629637
this.promoteValues =
630638
typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
@@ -648,6 +656,7 @@ export class BinMsg {
648656
// Allow the return of raw documents instead of parsing
649657
const raw = options.raw || false;
650658
const documentsReturnedIn = options.documentsReturnedIn || null;
659+
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
651660
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
652661
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
653662
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
@@ -656,6 +665,7 @@ export class BinMsg {
656665

657666
// Set up the options
658667
const bsonOptions: BSONSerializeOptions = {
668+
useBigInt64,
659669
promoteLongs,
660670
promoteValues,
661671
promoteBuffers,

src/cmap/connection.ts

+1
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ function write(
661661
command: !!options.command,
662662

663663
// for BSON parsing
664+
useBigInt64: typeof options.useBigInt64 === 'boolean' ? options.useBigInt64 : false,
664665
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
665666
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
666667
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,

src/connection_string.ts

+12
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,15 @@ export function parseOptions(
255255
mongoClient = undefined;
256256
}
257257

258+
// validate BSONOptions
259+
if (options.useBigInt64 && typeof options.promoteLongs === 'boolean' && !options.promoteLongs) {
260+
throw new MongoAPIError('Must request either bigint or Long for int64 deserialization');
261+
}
262+
263+
if (options.useBigInt64 && typeof options.promoteValues === 'boolean' && !options.promoteValues) {
264+
throw new MongoAPIError('Must request either bigint or Long for int64 deserialization');
265+
}
266+
258267
const url = new ConnectionString(uri);
259268
const { hosts, isSRV } = url;
260269

@@ -955,6 +964,9 @@ export const OPTIONS = {
955964
promoteValues: {
956965
type: 'boolean'
957966
},
967+
useBigInt64: {
968+
type: 'boolean'
969+
},
958970
proxyHost: {
959971
type: 'string'
960972
},

src/cursor/abstract_cursor.ts

+4
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@ export abstract class AbstractCursor<
642642
this[kId] =
643643
typeof response.cursor.id === 'number'
644644
? Long.fromNumber(response.cursor.id)
645+
: typeof response.cursor.id === 'bigint'
646+
? Long.fromBigInt(response.cursor.id)
645647
: response.cursor.id;
646648

647649
if (response.cursor.ns) {
@@ -741,6 +743,8 @@ export function next<T>(
741743
const cursorId =
742744
typeof response.cursor.id === 'number'
743745
? Long.fromNumber(response.cursor.id)
746+
: typeof response.cursor.id === 'bigint'
747+
? Long.fromBigInt(response.cursor.id)
744748
: response.cursor.id;
745749

746750
cursor[kDocuments].pushMany(response.cursor.nextBatch);

src/db.ts

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const DB_OPTIONS_ALLOW_LIST = [
5757
'readConcern',
5858
'retryMiliSeconds',
5959
'numberOfRetries',
60+
'useBigInt64',
6061
'promoteBuffers',
6162
'promoteLongs',
6263
'bsonRegExp',

src/mongo_types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export type ArrayElement<Type> = Type extends ReadonlyArray<infer Item> ? Item :
176176
export type SchemaMember<T, V> = { [P in keyof T]?: V } | { [key: string]: V };
177177

178178
/** @public */
179-
export type IntegerType = number | Int32 | Long;
179+
export type IntegerType = number | Int32 | Long | bigint;
180180

181181
/** @public */
182182
export type NumericType = IntegerType | Decimal128 | Double;
@@ -454,6 +454,7 @@ export type NestedPaths<Type, Depth extends number[]> = Depth['length'] extends
454454
: Type extends
455455
| string
456456
| number
457+
| bigint
457458
| boolean
458459
| Date
459460
| RegExp

src/operations/create_collection.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const ILLEGAL_COMMAND_FIELDS = new Set([
2323
'writeConcern',
2424
'raw',
2525
'fieldsAsRaw',
26+
'useBigInt64',
2627
'promoteLongs',
2728
'promoteValues',
2829
'promoteBuffers',

src/sdam/monitor.ts

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
120120
// force BSON serialization options
121121
{
122122
raw: false,
123+
useBigInt64: false,
123124
promoteLongs: true,
124125
promoteValues: true,
125126
promoteBuffers: true

src/sessions.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
307307
if (
308308
!clusterTime.signature ||
309309
clusterTime.signature.hash?._bsontype !== 'Binary' ||
310-
(typeof clusterTime.signature.keyId !== 'number' &&
310+
(typeof clusterTime.signature.keyId !== 'bigint' &&
311+
typeof clusterTime.signature.keyId !== 'number' &&
311312
clusterTime.signature.keyId?._bsontype !== 'Long') // apparently we decode the key to number?
312313
) {
313314
throw new MongoInvalidArgumentError(

test/integration/change-streams/change_stream.test.ts

+60-4
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,14 @@ describe('Change Streams', function () {
162162
it('should close the listeners after the cursor is closed', {
163163
metadata: { requires: { topology: 'replicaset' } },
164164
async test() {
165+
const collection = db.collection('closesListeners');
166+
const changeStream = collection.watch(pipeline);
165167
const willBeChanges = on(changeStream, 'change');
166168
await once(changeStream.cursor, 'init');
167169
await collection.insertOne({ a: 1 });
168170

169171
await willBeChanges.next();
170-
expect(changeStream.cursorStream.listenerCount('data')).to.equal(1);
172+
expect(changeStream.cursorStream?.listenerCount('data')).to.equal(1);
171173

172174
await changeStream.close();
173175
expect(changeStream.cursorStream).to.not.exist;
@@ -1670,7 +1672,7 @@ describe('Change Streams', function () {
16701672
it('does not convert Longs to numbers', {
16711673
metadata: { requires: { topology: '!single' } },
16721674
test: async function () {
1673-
cs = collection.watch([], { promoteLongs: true });
1675+
cs = collection.watch([], { promoteLongs: true, useBigInt64: false });
16741676

16751677
const willBeChange = once(cs, 'change').then(args => args[0]);
16761678
await once(cs.cursor, 'init');
@@ -1689,7 +1691,7 @@ describe('Change Streams', function () {
16891691
it('converts Long values to native numbers', {
16901692
metadata: { requires: { topology: '!single' } },
16911693
test: async function () {
1692-
cs = collection.watch([], { promoteLongs: false });
1694+
cs = collection.watch([], { promoteLongs: false, useBigInt64: false });
16931695

16941696
const willBeChange = once(cs, 'change').then(args => args[0]);
16951697
await once(cs.cursor, 'init');
@@ -1707,7 +1709,7 @@ describe('Change Streams', function () {
17071709
it('defaults to true', {
17081710
metadata: { requires: { topology: '!single' } },
17091711
test: async function () {
1710-
cs = collection.watch([]);
1712+
cs = collection.watch([], { useBigInt64: false });
17111713

17121714
const willBeChange = once(cs, 'change').then(args => args[0]);
17131715
await once(cs.cursor, 'init');
@@ -1722,6 +1724,60 @@ describe('Change Streams', function () {
17221724
});
17231725
});
17241726

1727+
context('useBigInt64', () => {
1728+
const useBigInt64FalseTest = async (options: ChangeStreamOptions) => {
1729+
cs = collection.watch([], options);
1730+
const willBeChange = once(cs, 'change').then(args => args[0]);
1731+
await once(cs.cursor, 'init');
1732+
1733+
await collection.insertOne({ a: Long.fromNumber(10) });
1734+
1735+
const change = await willBeChange;
1736+
1737+
expect(typeof change.fullDocument.a).to.equal('number');
1738+
};
1739+
1740+
context('when set to false', function () {
1741+
it('converts Long to number', {
1742+
metadata: {
1743+
requires: { topology: '!single' }
1744+
},
1745+
test: async function () {
1746+
await useBigInt64FalseTest({ useBigInt64: false });
1747+
}
1748+
});
1749+
});
1750+
1751+
context('when set to true', function () {
1752+
it('converts Long to bigint', {
1753+
metadata: {
1754+
requires: { topology: '!single' }
1755+
},
1756+
test: async function () {
1757+
cs = collection.watch([], { useBigInt64: true });
1758+
const willBeChange = once(cs, 'change').then(args => args[0]);
1759+
await once(cs.cursor, 'init');
1760+
1761+
await collection.insertOne({ a: Long.fromNumber(10) });
1762+
1763+
const change = await willBeChange;
1764+
1765+
expect(change.fullDocument).property('a').to.be.a('bigint');
1766+
expect(change.fullDocument).property('a', 10n);
1767+
}
1768+
});
1769+
});
1770+
1771+
context('when unset', function () {
1772+
it('defaults to false', {
1773+
metadata: { requires: { topology: '!single' } },
1774+
test: async function () {
1775+
await useBigInt64FalseTest({});
1776+
}
1777+
});
1778+
});
1779+
});
1780+
17251781
context('invalid options', function () {
17261782
it('does not send invalid options on the aggregate command', {
17271783
metadata: { requires: { topology: '!single' } },

0 commit comments

Comments
 (0)