Skip to content

Commit

Permalink
feat(NODE-4877): Add support for useBigInt64 (#3519)
Browse files Browse the repository at this point in the history
Co-authored-by: Neal Beeken <neal.beeken@mongodb.com>
Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
  • Loading branch information
3 people committed Feb 23, 2023
1 parent 10146a4 commit 917668c
Show file tree
Hide file tree
Showing 22 changed files with 670 additions and 378 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -87,3 +87,4 @@ etc/docs/build
!docs/**/*.png
!docs/**/*.css
!docs/**/*.js
.nvmrc
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -25,7 +25,7 @@
"email": "dbx-node@mongodb.com"
},
"dependencies": {
"bson": "^5.0.0",
"bson": "^5.0.1",
"mongodb-connection-string-url": "^2.6.0",
"socks": "^2.7.1"
},
Expand Down
4 changes: 3 additions & 1 deletion src/bson.ts
Expand Up @@ -36,7 +36,6 @@ export interface BSONSerializeOptions
| 'allowObjectSmallerThanBufferSize'
| 'index'
| 'validation'
| 'useBigInt64'
> {
/**
* Enabling the raw option will return a [Node.js Buffer](https://nodejs.org/api/buffer.html)
Expand Down Expand Up @@ -67,6 +66,7 @@ export interface BSONSerializeOptions
export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSerializeOptions {
const {
fieldsAsRaw,
useBigInt64,
promoteValues,
promoteBuffers,
promoteLongs,
Expand All @@ -78,6 +78,7 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
} = options;
return {
fieldsAsRaw,
useBigInt64,
promoteValues,
promoteBuffers,
promoteLongs,
Expand All @@ -102,6 +103,7 @@ export function resolveBSONOptions(
const parentOptions = parent?.bsonOptions;
return {
raw: options?.raw ?? parentOptions?.raw ?? false,
useBigInt64: options?.useBigInt64 ?? parentOptions?.useBigInt64 ?? false,
promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,
Expand Down
1 change: 1 addition & 0 deletions src/cmap/auth/mongodb_aws.ts
Expand Up @@ -21,6 +21,7 @@ const AWS_RELATIVE_URI = 'http://169.254.170.2';
const AWS_EC2_URI = 'http://169.254.169.254';
const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
const bsonOptions: BSONSerializeOptions = {
useBigInt64: false,
promoteLongs: true,
promoteValues: true,
promoteBuffers: false,
Expand Down
10 changes: 10 additions & 0 deletions src/cmap/commands.ts
Expand Up @@ -304,6 +304,7 @@ export class Response {
queryFailure?: boolean;
shardConfigStale?: boolean;
awaitCapable?: boolean;
useBigInt64: boolean;
promoteLongs: boolean;
promoteValues: boolean;
promoteBuffers: boolean;
Expand All @@ -320,6 +321,7 @@ export class Response {
this.raw = message;
this.data = msgBody;
this.opts = opts ?? {
useBigInt64: false,
promoteLongs: true,
promoteValues: true,
promoteBuffers: false,
Expand All @@ -334,6 +336,7 @@ export class Response {
this.fromCompressed = msgHeader.fromCompressed;

// Flag values
this.useBigInt64 = typeof this.opts.useBigInt64 === 'boolean' ? this.opts.useBigInt64 : false;
this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
this.promoteValues =
typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
Expand All @@ -354,6 +357,7 @@ export class Response {
// Allow the return of raw documents instead of parsing
const raw = options.raw || false;
const documentsReturnedIn = options.documentsReturnedIn || null;
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
Expand All @@ -362,6 +366,7 @@ export class Response {

// Set up the options
const _options: BSONSerializeOptions = {
useBigInt64,
promoteLongs,
promoteValues,
promoteBuffers,
Expand Down Expand Up @@ -590,6 +595,7 @@ export class BinMsg {
checksumPresent: boolean;
moreToCome: boolean;
exhaustAllowed: boolean;
useBigInt64: boolean;
promoteLongs: boolean;
promoteValues: boolean;
promoteBuffers: boolean;
Expand All @@ -607,6 +613,7 @@ export class BinMsg {
this.raw = message;
this.data = msgBody;
this.opts = opts ?? {
useBigInt64: false,
promoteLongs: true,
promoteValues: true,
promoteBuffers: false,
Expand All @@ -625,6 +632,7 @@ export class BinMsg {
this.checksumPresent = (this.responseFlags & OPTS_CHECKSUM_PRESENT) !== 0;
this.moreToCome = (this.responseFlags & OPTS_MORE_TO_COME) !== 0;
this.exhaustAllowed = (this.responseFlags & OPTS_EXHAUST_ALLOWED) !== 0;
this.useBigInt64 = typeof this.opts.useBigInt64 === 'boolean' ? this.opts.useBigInt64 : false;
this.promoteLongs = typeof this.opts.promoteLongs === 'boolean' ? this.opts.promoteLongs : true;
this.promoteValues =
typeof this.opts.promoteValues === 'boolean' ? this.opts.promoteValues : true;
Expand All @@ -648,6 +656,7 @@ export class BinMsg {
// Allow the return of raw documents instead of parsing
const raw = options.raw || false;
const documentsReturnedIn = options.documentsReturnedIn || null;
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
Expand All @@ -656,6 +665,7 @@ export class BinMsg {

// Set up the options
const bsonOptions: BSONSerializeOptions = {
useBigInt64,
promoteLongs,
promoteValues,
promoteBuffers,
Expand Down
1 change: 1 addition & 0 deletions src/cmap/connection.ts
Expand Up @@ -661,6 +661,7 @@ function write(
command: !!options.command,

// for BSON parsing
useBigInt64: typeof options.useBigInt64 === 'boolean' ? options.useBigInt64 : false,
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
Expand Down
12 changes: 12 additions & 0 deletions src/connection_string.ts
Expand Up @@ -255,6 +255,15 @@ export function parseOptions(
mongoClient = undefined;
}

// validate BSONOptions
if (options.useBigInt64 && typeof options.promoteLongs === 'boolean' && !options.promoteLongs) {
throw new MongoAPIError('Must request either bigint or Long for int64 deserialization');
}

if (options.useBigInt64 && typeof options.promoteValues === 'boolean' && !options.promoteValues) {
throw new MongoAPIError('Must request either bigint or Long for int64 deserialization');
}

const url = new ConnectionString(uri);
const { hosts, isSRV } = url;

Expand Down Expand Up @@ -955,6 +964,9 @@ export const OPTIONS = {
promoteValues: {
type: 'boolean'
},
useBigInt64: {
type: 'boolean'
},
proxyHost: {
type: 'string'
},
Expand Down
4 changes: 4 additions & 0 deletions src/cursor/abstract_cursor.ts
Expand Up @@ -642,6 +642,8 @@ export abstract class AbstractCursor<
this[kId] =
typeof response.cursor.id === 'number'
? Long.fromNumber(response.cursor.id)
: typeof response.cursor.id === 'bigint'
? Long.fromBigInt(response.cursor.id)
: response.cursor.id;

if (response.cursor.ns) {
Expand Down Expand Up @@ -741,6 +743,8 @@ export function next<T>(
const cursorId =
typeof response.cursor.id === 'number'
? Long.fromNumber(response.cursor.id)
: typeof response.cursor.id === 'bigint'
? Long.fromBigInt(response.cursor.id)
: response.cursor.id;

cursor[kDocuments].pushMany(response.cursor.nextBatch);
Expand Down
1 change: 1 addition & 0 deletions src/db.ts
Expand Up @@ -57,6 +57,7 @@ const DB_OPTIONS_ALLOW_LIST = [
'readConcern',
'retryMiliSeconds',
'numberOfRetries',
'useBigInt64',
'promoteBuffers',
'promoteLongs',
'bsonRegExp',
Expand Down
3 changes: 2 additions & 1 deletion src/mongo_types.ts
Expand Up @@ -176,7 +176,7 @@ export type ArrayElement<Type> = Type extends ReadonlyArray<infer Item> ? Item :
export type SchemaMember<T, V> = { [P in keyof T]?: V } | { [key: string]: V };

/** @public */
export type IntegerType = number | Int32 | Long;
export type IntegerType = number | Int32 | Long | bigint;

/** @public */
export type NumericType = IntegerType | Decimal128 | Double;
Expand Down Expand Up @@ -454,6 +454,7 @@ export type NestedPaths<Type, Depth extends number[]> = Depth['length'] extends
: Type extends
| string
| number
| bigint
| boolean
| Date
| RegExp
Expand Down
1 change: 1 addition & 0 deletions src/operations/create_collection.ts
Expand Up @@ -23,6 +23,7 @@ const ILLEGAL_COMMAND_FIELDS = new Set([
'writeConcern',
'raw',
'fieldsAsRaw',
'useBigInt64',
'promoteLongs',
'promoteValues',
'promoteBuffers',
Expand Down
1 change: 1 addition & 0 deletions src/sdam/monitor.ts
Expand Up @@ -120,6 +120,7 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
// force BSON serialization options
{
raw: false,
useBigInt64: false,
promoteLongs: true,
promoteValues: true,
promoteBuffers: true
Expand Down
3 changes: 2 additions & 1 deletion src/sessions.ts
Expand Up @@ -307,7 +307,8 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
if (
!clusterTime.signature ||
clusterTime.signature.hash?._bsontype !== 'Binary' ||
(typeof clusterTime.signature.keyId !== 'number' &&
(typeof clusterTime.signature.keyId !== 'bigint' &&
typeof clusterTime.signature.keyId !== 'number' &&
clusterTime.signature.keyId?._bsontype !== 'Long') // apparently we decode the key to number?
) {
throw new MongoInvalidArgumentError(
Expand Down
64 changes: 60 additions & 4 deletions test/integration/change-streams/change_stream.test.ts
Expand Up @@ -162,12 +162,14 @@ describe('Change Streams', function () {
it('should close the listeners after the cursor is closed', {
metadata: { requires: { topology: 'replicaset' } },
async test() {
const collection = db.collection('closesListeners');
const changeStream = collection.watch(pipeline);
const willBeChanges = on(changeStream, 'change');
await once(changeStream.cursor, 'init');
await collection.insertOne({ a: 1 });

await willBeChanges.next();
expect(changeStream.cursorStream.listenerCount('data')).to.equal(1);
expect(changeStream.cursorStream?.listenerCount('data')).to.equal(1);

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

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

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

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

context('useBigInt64', () => {
const useBigInt64FalseTest = async (options: ChangeStreamOptions) => {
cs = collection.watch([], options);
const willBeChange = once(cs, 'change').then(args => args[0]);
await once(cs.cursor, 'init');

await collection.insertOne({ a: Long.fromNumber(10) });

const change = await willBeChange;

expect(typeof change.fullDocument.a).to.equal('number');
};

context('when set to false', function () {
it('converts Long to number', {
metadata: {
requires: { topology: '!single' }
},
test: async function () {
await useBigInt64FalseTest({ useBigInt64: false });
}
});
});

context('when set to true', function () {
it('converts Long to bigint', {
metadata: {
requires: { topology: '!single' }
},
test: async function () {
cs = collection.watch([], { useBigInt64: true });
const willBeChange = once(cs, 'change').then(args => args[0]);
await once(cs.cursor, 'init');

await collection.insertOne({ a: Long.fromNumber(10) });

const change = await willBeChange;

expect(change.fullDocument).property('a').to.be.a('bigint');
expect(change.fullDocument).property('a', 10n);
}
});
});

context('when unset', function () {
it('defaults to false', {
metadata: { requires: { topology: '!single' } },
test: async function () {
await useBigInt64FalseTest({});
}
});
});
});

context('invalid options', function () {
it('does not send invalid options on the aggregate command', {
metadata: { requires: { topology: '!single' } },
Expand Down

0 comments on commit 917668c

Please sign in to comment.