From 917668c0bc42dbfa0a00869daa223f953f28c2e2 Mon Sep 17 00:00:00 2001 From: Warren James Date: Thu, 23 Feb 2023 10:42:37 -0500 Subject: [PATCH] feat(NODE-4877): Add support for useBigInt64 (#3519) Co-authored-by: Neal Beeken Co-authored-by: Bailey Pearson --- .gitignore | 1 + package-lock.json | 14 +- package.json | 2 +- src/bson.ts | 4 +- src/cmap/auth/mongodb_aws.ts | 1 + src/cmap/commands.ts | 10 + src/cmap/connection.ts | 1 + src/connection_string.ts | 12 + src/cursor/abstract_cursor.ts | 4 + src/db.ts | 1 + src/mongo_types.ts | 3 +- src/operations/create_collection.ts | 1 + src/sdam/monitor.ts | 1 + src/sessions.ts | 3 +- .../change-streams/change_stream.test.ts | 64 ++++- test/integration/crud/explain.test.js | 179 ++++++------ test/integration/crud/insert.test.js | 240 +++++++--------- .../bson-options/promote_values.test.js | 220 ++++++--------- .../bson-options/use_bigint_64.test.ts | 262 ++++++++++++++++++ test/tools/unified-spec-runner/match.ts | 5 +- test/types/bson.test-d.ts | 2 + test/unit/sessions.test.js | 18 +- 22 files changed, 670 insertions(+), 378 deletions(-) create mode 100644 test/integration/node-specific/bson-options/use_bigint_64.test.ts diff --git a/.gitignore b/.gitignore index 43e6877bad..6bcaaae526 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,4 @@ etc/docs/build !docs/**/*.png !docs/**/*.css !docs/**/*.js +.nvmrc diff --git a/package-lock.json b/package-lock.json index dfab61e7ae..7a281b2482 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "5.0.1", "license": "Apache-2.0", "dependencies": { - "bson": "^5.0.0", + "bson": "^5.0.1", "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, @@ -3143,9 +3143,9 @@ } }, "node_modules/bson": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.0.tgz", - "integrity": "sha512-EL2KpZdyhshyyptj6pnQfnFKPoncD9KwZYvgmj/FXQiOUU1HWTHWmBOP4TZXU3YzStcI5qgpIl68YnMo16s26A==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.1.tgz", + "integrity": "sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==", "engines": { "node": ">=14.20.1" } @@ -12021,9 +12021,9 @@ } }, "bson": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.0.tgz", - "integrity": "sha512-EL2KpZdyhshyyptj6pnQfnFKPoncD9KwZYvgmj/FXQiOUU1HWTHWmBOP4TZXU3YzStcI5qgpIl68YnMo16s26A==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.0.1.tgz", + "integrity": "sha512-y09gBGusgHtinMon/GVbv1J6FrXhnr/+6hqLlSmEFzkz6PodqF6TxjyvfvY3AfO+oG1mgUtbC86xSbOlwvM62Q==" }, "buffer": { "version": "5.7.1", diff --git a/package.json b/package.json index 18978bc1fd..1fcada1677 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/bson.ts b/src/bson.ts index 8ef9bc2a82..7f335f1c13 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -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) @@ -67,6 +66,7 @@ export interface BSONSerializeOptions export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSerializeOptions { const { fieldsAsRaw, + useBigInt64, promoteValues, promoteBuffers, promoteLongs, @@ -78,6 +78,7 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe } = options; return { fieldsAsRaw, + useBigInt64, promoteValues, promoteBuffers, promoteLongs, @@ -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, diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index c932da6a2e..5d9007dcb5 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -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, diff --git a/src/cmap/commands.ts b/src/cmap/commands.ts index fa822aaac5..6364d33d72 100644 --- a/src/cmap/commands.ts +++ b/src/cmap/commands.ts @@ -304,6 +304,7 @@ export class Response { queryFailure?: boolean; shardConfigStale?: boolean; awaitCapable?: boolean; + useBigInt64: boolean; promoteLongs: boolean; promoteValues: boolean; promoteBuffers: boolean; @@ -320,6 +321,7 @@ export class Response { this.raw = message; this.data = msgBody; this.opts = opts ?? { + useBigInt64: false, promoteLongs: true, promoteValues: true, promoteBuffers: false, @@ -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; @@ -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; @@ -362,6 +366,7 @@ export class Response { // Set up the options const _options: BSONSerializeOptions = { + useBigInt64, promoteLongs, promoteValues, promoteBuffers, @@ -590,6 +595,7 @@ export class BinMsg { checksumPresent: boolean; moreToCome: boolean; exhaustAllowed: boolean; + useBigInt64: boolean; promoteLongs: boolean; promoteValues: boolean; promoteBuffers: boolean; @@ -607,6 +613,7 @@ export class BinMsg { this.raw = message; this.data = msgBody; this.opts = opts ?? { + useBigInt64: false, promoteLongs: true, promoteValues: true, promoteBuffers: false, @@ -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; @@ -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; @@ -656,6 +665,7 @@ export class BinMsg { // Set up the options const bsonOptions: BSONSerializeOptions = { + useBigInt64, promoteLongs, promoteValues, promoteBuffers, diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 2b0e340e33..4ee55d3e73 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -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, diff --git a/src/connection_string.ts b/src/connection_string.ts index 75d6298fff..b282afc489 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -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; @@ -955,6 +964,9 @@ export const OPTIONS = { promoteValues: { type: 'boolean' }, + useBigInt64: { + type: 'boolean' + }, proxyHost: { type: 'string' }, diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index fe0b6ffaef..a115d971f5 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -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) { @@ -741,6 +743,8 @@ export function next( 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); diff --git a/src/db.ts b/src/db.ts index 67c49dd96e..4381caa795 100644 --- a/src/db.ts +++ b/src/db.ts @@ -57,6 +57,7 @@ const DB_OPTIONS_ALLOW_LIST = [ 'readConcern', 'retryMiliSeconds', 'numberOfRetries', + 'useBigInt64', 'promoteBuffers', 'promoteLongs', 'bsonRegExp', diff --git a/src/mongo_types.ts b/src/mongo_types.ts index 0676a31c22..ceee3aa69e 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -176,7 +176,7 @@ export type ArrayElement = Type extends ReadonlyArray ? Item : export type SchemaMember = { [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; @@ -454,6 +454,7 @@ export type NestedPaths = Depth['length'] extends : Type extends | string | number + | bigint | boolean | Date | RegExp diff --git a/src/operations/create_collection.ts b/src/operations/create_collection.ts index 1b32422746..1d51d9aff6 100644 --- a/src/operations/create_collection.ts +++ b/src/operations/create_collection.ts @@ -23,6 +23,7 @@ const ILLEGAL_COMMAND_FIELDS = new Set([ 'writeConcern', 'raw', 'fieldsAsRaw', + 'useBigInt64', 'promoteLongs', 'promoteValues', 'promoteBuffers', diff --git a/src/sdam/monitor.ts b/src/sdam/monitor.ts index 2625efa1e7..ebb032a9d9 100644 --- a/src/sdam/monitor.ts +++ b/src/sdam/monitor.ts @@ -120,6 +120,7 @@ export class Monitor extends TypedEventEmitter { // force BSON serialization options { raw: false, + useBigInt64: false, promoteLongs: true, promoteValues: true, promoteBuffers: true diff --git a/src/sessions.ts b/src/sessions.ts index 9e42795229..3f76044d2a 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -307,7 +307,8 @@ export class ClientSession extends TypedEventEmitter { 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( diff --git a/test/integration/change-streams/change_stream.test.ts b/test/integration/change-streams/change_stream.test.ts index a0533023be..7b825b6f44 100644 --- a/test/integration/change-streams/change_stream.test.ts +++ b/test/integration/change-streams/change_stream.test.ts @@ -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; @@ -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'); @@ -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'); @@ -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'); @@ -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' } }, diff --git a/test/integration/crud/explain.test.js b/test/integration/crud/explain.test.js index f6f26d6711..aa9c6a2ff5 100644 --- a/test/integration/crud/explain.test.js +++ b/test/integration/crud/explain.test.js @@ -564,23 +564,26 @@ describe('Explain', function () { } }); - it('should honor boolean explain with aggregate', function (done) { + it('should honor boolean explain with aggregate', async function () { const db = client.db('shouldHonorBooleanExplainWithAggregate'); const collection = db.collection('test'); - collection.insertOne({ a: 1 }, (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - collection - .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain: true }) - .toArray((err, docs) => { - expect(err).to.not.exist; - const result = JSON.stringify(docs[0]); - expect(result).to.include('"queryPlanner"'); - expect(result).to.include('"executionStats"'); - done(); - }); - }); + await collection.insertOne({ a: 1 }); + const aggResult = await collection + .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain: true }) + .toArray(); + + if (aggResult[0].stages) { + expect(aggResult[0].stages).to.have.length.gte(1); + expect(aggResult[0].stages[0]).to.have.property('$cursor'); + expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats'); + } else if (aggResult[0].$cursor) { + expect(aggResult[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult[0].$cursor).to.have.property('executionStats'); + } else { + expect(aggResult[0]).to.have.property('queryPlanner'); + expect(aggResult[0]).to.have.property('executionStats'); + } }); it('should honor string explain with aggregate', { @@ -589,47 +592,51 @@ describe('Explain', function () { mongodb: '>=3.6.0' } }, - test: function (done) { + test: async function () { const db = client.db('shouldHonorStringExplainWithAggregate'); const collection = db.collection('test'); - collection.insertOne({ a: 1 }, (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - collection - .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { - explain: 'executionStats' - }) - .toArray((err, docs) => { - expect(err).to.not.exist; - const result = JSON.stringify(docs[0]); - expect(result).to.include('"queryPlanner"'); - expect(result).to.include('"executionStats"'); - done(); - }); - }); + await collection.insertOne({ a: 1 }); + const aggResult = await collection + .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { + explain: 'executionStats' + }) + .toArray(); + if (aggResult[0].stages) { + expect(aggResult[0].stages).to.have.length.gte(1); + expect(aggResult[0].stages[0]).to.have.property('$cursor'); + expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats'); + } else if (aggResult[0].$cursor) { + expect(aggResult[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult[0].$cursor).to.have.property('executionStats'); + } else { + expect(aggResult[0]).to.have.property('queryPlanner'); + expect(aggResult[0]).to.have.property('executionStats'); + } } }); - it('should honor boolean explain specified on cursor with aggregate', function (done) { + it('should honor boolean explain specified on cursor with aggregate', async function () { const db = client.db('shouldHonorBooleanExplainSpecifiedOnCursor'); const collection = db.collection('test'); - collection.insertOne({ a: 1 }, (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - collection - .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }]) - .explain(false, (err, res) => { - expect(err).to.not.exist; - const result = JSON.stringify(res); - expect(result).to.include('"queryPlanner"'); - expect(result).not.to.include('"executionStats"'); - done(); - }); - }); + await collection.insertOne({ a: 1 }); + const aggResult = await collection + .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }]) + .explain(false); + if (aggResult && aggResult.stages) { + expect(aggResult.stages).to.have.length.gte(1); + expect(aggResult.stages[0]).to.have.property('$cursor'); + expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult.stages[0].$cursor).to.not.have.property('executionStats'); + } else if (aggResult.$cursor) { + expect(aggResult.$cursor).to.have.property('queryPlanner'); + expect(aggResult.$cursor).to.not.have.property('executionStats'); + } else { + expect(aggResult).to.have.property('queryPlanner'); + expect(aggResult).to.not.have.property('executionStats'); + } }); it('should honor string explain specified on cursor with aggregate', { @@ -638,46 +645,44 @@ describe('Explain', function () { mongodb: '>=3.6' } }, - test: function (done) { + test: async function () { const db = client.db('shouldHonorStringExplainSpecifiedOnCursor'); const collection = db.collection('test'); - collection.insertOne({ a: 1 }, (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - collection - .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }]) - .explain('allPlansExecution', (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - const result = JSON.stringify(res); - expect(result).to.include('"queryPlanner"'); - expect(result).to.include('"executionStats"'); - done(); - }); - }); + await collection.insertOne({ a: 1 }); + const aggResult = await collection + .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }]) + .explain('allPlansExecution'); + + if (aggResult && aggResult.stages) { + expect(aggResult.stages).to.have.length.gte(1); + expect(aggResult.stages[0]).to.have.property('$cursor'); + expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult.stages[0].$cursor).to.have.property('executionStats'); + } else { + expect(aggResult).to.have.property('queryPlanner'); + expect(aggResult).to.have.property('executionStats'); + } } }); - it('should honor legacy explain with aggregate', function (done) { + it('should honor legacy explain with aggregate', async function () { const db = client.db('shouldHonorLegacyExplainWithAggregate'); const collection = db.collection('test'); - collection.insertOne({ a: 1 }, (err, res) => { - expect(err).to.not.exist; - expect(res).to.exist; - - collection - .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }]) - .explain((err, res) => { - expect(err).to.not.exist; - const result = JSON.stringify(res); - expect(result).to.include('"queryPlanner"'); - expect(result).to.include('"executionStats"'); - done(); - }); - }); + await collection.insertOne({ a: 1 }); + const aggResult = await collection + .aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }]) + .explain(); + if (aggResult && aggResult.stages) { + expect(aggResult.stages).to.have.length.gte(1); + expect(aggResult.stages[0]).to.have.property('$cursor'); + expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner'); + expect(aggResult.stages[0].$cursor).to.have.property('executionStats'); + } else { + expect(aggResult).to.have.property('queryPlanner'); + expect(aggResult).to.have.property('executionStats'); + } }); it('should throw a catchable error with invalid explain string', { @@ -686,18 +691,16 @@ describe('Explain', function () { mongodb: '>=3.4' } }, - test: function (done) { + test: async function () { const db = client.db('shouldThrowCatchableError'); const collection = db.collection('test'); - collection - .find({ a: 1 }) - .explain('invalidExplain') - .then(() => done(new Error('expected explain to fail but it succeeded'))) - .catch(err => { - expect(err).to.exist; - expect(err).to.be.instanceOf(MongoServerError); - done(); - }); + try { + await collection.find({ a: 1 }).explain('invalidExplain'); + expect.fail(new Error('Expected explain to fail but it succeeded')); + } catch (e) { + expect(e).to.exist; + expect(e).to.be.instanceOf(MongoServerError); + } } }); }); diff --git a/test/integration/crud/insert.test.js b/test/integration/crud/insert.test.js index 1c95ee78b8..b8b0305b70 100644 --- a/test/integration/crud/insert.test.js +++ b/test/integration/crud/insert.test.js @@ -428,25 +428,20 @@ describe('crud - insert', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - var collection = db.collection('test_to_json_for_long'); + test: async function () { + const configuration = this.configuration; + const client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); + await client.connect(); + const db = client.db(configuration.db); + const collection = db.collection('test_to_json_for_long'); + await collection.insert( + [{ value: Long.fromNumber(32222432) }], + configuration.writeConcernMax() + ); + const findResult = await collection.findOne({}); + expect(findResult.value).to.deep.equal(32222432); - collection.insert( - [{ value: Long.fromNumber(32222432) }], - configuration.writeConcernMax(), - function (err, ids) { - test.ok(ids); - collection.findOne({}, function (err, item) { - test.equal(32222432, item.value); - client.close(done); - }); - } - ); - }); + await client.close(); } }); @@ -457,29 +452,22 @@ describe('crud - insert', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - var collection = db.collection('test_insert_and_query_timestamp'); + test: async function () { + const configuration = this.configuration; + const client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); + await client.connect(); + const db = client.db(configuration.db); + const collection = db.collection('test_insert_and_query_timestamp'); + await collection.insertOne( + { i: Timestamp.fromNumber(100), j: Long.fromNumber(200) }, + configuration.writeConcernMax() + ); + const findResult = await collection.findOne({}); + expect(findResult.i._bsontype).equals('Timestamp'); + expect(findResult.i.toInt(), 100); + expect(findResult.j, 200); - // Insert the update - collection.insert( - { i: Timestamp.fromNumber(100), j: Long.fromNumber(200) }, - configuration.writeConcernMax(), - function (err, r) { - test.ok(r); - // Locate document - collection.findOne({}, function (err, item) { - test.ok(item.i._bsontype === 'Timestamp'); - test.equal(100, item.i.toInt()); - test.equal(200, item.j); - client.close(done); - }); - } - ); - }); + await client.close(); } }); @@ -1705,7 +1693,7 @@ describe('crud - insert', function () { try { db.collection(k.toString()); test.fail(false); - } catch (err) {} // eslint-disable-line + } catch (err) { } // eslint-disable-line client.close(done); }); @@ -1719,34 +1707,26 @@ describe('crud - insert', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; - var o = configuration.writeConcernMax(); + test: async function () { + const configuration = this.configuration; + const o = configuration.writeConcernMax(); o.promoteLongs = false; - var client = configuration.newClient(configuration.writeConcernMax(), { + const client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1, promoteLongs: false }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteLong').insert( - { - doc: Long.fromNumber(10), - array: [[Long.fromNumber(10)]] - }, - function (err, doc) { - expect(err).to.not.exist; - test.ok(doc); - - db.collection('shouldCorrectlyHonorPromoteLong').findOne(function (err, doc) { - expect(err).to.not.exist; - test.ok(doc.doc._bsontype === 'Long'); - test.ok(doc.array[0][0]._bsontype === 'Long'); - client.close(done); - }); - } - ); + await client.connect(); + const db = client.db(configuration.db); + await db.collection('shouldCorrectlyHonorPromoteLong').insertOne({ + doc: Long.fromNumber(10), + array: [[Long.fromNumber(10)]] }); + const doc = await db.collection('shouldCorrectlyHonorPromoteLong').findOne(); + + expect(doc.doc._bsontype === 'Long'); + expect(doc.array[0][0]._bsontype === 'Long'); + + await client.close(); } }); @@ -1757,61 +1737,54 @@ describe('crud - insert', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; - var o = configuration.writeConcernMax(); + test: async function () { + const configuration = this.configuration; + const o = configuration.writeConcernMax(); o.promoteLongs = false; - var client = configuration.newClient(configuration.writeConcernMax(), { + const client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1, promoteLongs: false }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteLongFalseNativeBSONWithGetMore').insertMany( - [ - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) }, - { a: Long.fromNumber(10) } - ], - function (err, doc) { - expect(err).to.not.exist; - test.ok(doc); - - db.collection('shouldCorrectlyHonorPromoteLongFalseNativeBSONWithGetMore') - .find({}) - .batchSize(2) - .toArray(function (err, docs) { - expect(err).to.not.exist; - var doc = docs.pop(); - - test.ok(doc.a._bsontype === 'Long'); - client.close(done); - }); - } - ); - }); + await client.connect(); + const db = client.db(configuration.db); + await db + .collection('shouldCorrectlyHonorPromoteLongFalseNativeBSONWithGetMore') + .insertMany([ + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) }, + { a: Long.fromNumber(10) } + ]); + + const docs = await db + .collection('shouldCorrectlyHonorPromoteLongFalseNativeBSONWithGetMore') + .find({}) + .batchSize(2) + .toArray(); + const doc = docs.pop(); + expect(doc.a._bsontype).to.equal('Long'); + client.close(); } }); @@ -1906,36 +1879,23 @@ describe('crud - insert', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { + test: async function () { + const configuration = this.configuration; + const client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1, promoteLongs: false }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteLongFalseJSBSON').insert( - { - doc: Long.fromNumber(10), - array: [[Long.fromNumber(10)]] - }, - function (err, doc) { - expect(err).to.not.exist; - test.ok(doc); - - db.collection('shouldCorrectlyHonorPromoteLongFalseJSBSON').findOne(function ( - err, - doc - ) { - expect(err).to.not.exist; - expect(err).to.not.exist; - test.ok(doc.doc._bsontype === 'Long'); - test.ok(doc.array[0][0]._bsontype === 'Long'); - client.close(done); - }); - } - ); + await client.connect(); + const db = client.db(configuration.db); + await db.collection('shouldCorrectlyHonorPromoteLongFalseJSBSON').insertOne({ + doc: Long.fromNumber(10), + array: [[Long.fromNumber(10)]] }); + const doc = await db.collection('shouldCorrectlyHonorPromoteLongFalseJSBSON').findOne({}); + expect(doc.doc._bsontype).to.equal('Long'); + expect(doc.array[0][0]._bsontype).to.equal('Long'); + + await client.close(); } }); diff --git a/test/integration/node-specific/bson-options/promote_values.test.js b/test/integration/node-specific/bson-options/promote_values.test.js index 3ffed22364..81a41b27a7 100644 --- a/test/integration/node-specific/bson-options/promote_values.test.js +++ b/test/integration/node-specific/bson-options/promote_values.test.js @@ -15,38 +15,27 @@ describe('Promote Values', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { + test: async function () { + let configuration = this.configuration; + let client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1, promoteValues: false }); - - client.connect(function (err, client) { - var db = client.db(configuration.db); - - db.collection('shouldCorrectlyHonorPromoteValues').insert( - { - doc: Long.fromNumber(10), - int: 10, - double: 2.2222, - array: [[Long.fromNumber(10)]] - }, - function (err) { - expect(err).to.not.exist; - - db.collection('shouldCorrectlyHonorPromoteValues').findOne(function (err, doc) { - expect(err).to.not.exist; - - test.deepEqual(Long.fromNumber(10), doc.doc); - test.deepEqual(new Int32(10), doc.int); - test.deepEqual(new Double(2.2222), doc.double); - - client.close(done); - }); - } - ); + await client.connect(); + let db = client.db(configuration.db); + + await db.collection('shouldCorrectlyHonorPromoteValues').insertOne({ + doc: Long.fromNumber(10), + int: 10, + double: 2.2222, + array: [[Long.fromNumber(10)]] }); + let doc = await db.collection('shouldCorrectlyHonorPromoteValues').findOne(); + expect(Long.fromNumber(10)).deep.equals(doc.doc); + expect(new Int32(10)).deep.equals(doc.int); + expect(new Double(2.2222)).deep.equals(doc.double); + + await client.close(); } }); @@ -57,33 +46,25 @@ describe('Promote Values', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { + test: async function () { var configuration = this.configuration; const client = configuration.newClient({}, { promoteValues: false }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteValues').insert( - { - doc: Long.fromNumber(10), - int: 10, - double: 2.2222, - array: [[Long.fromNumber(10)]] - }, - function (err) { - expect(err).to.not.exist; - - db.collection('shouldCorrectlyHonorPromoteValues').findOne(function (err, doc) { - expect(err).to.not.exist; - - test.deepEqual(Long.fromNumber(10), doc.doc); - test.deepEqual(new Int32(10), doc.int); - test.deepEqual(new Double(2.2222), doc.double); - - client.close(done); - }); - } - ); + await client.connect(); + const db = client.db(configuration.db); + + await db.collection('shouldCorrectlyHonorPromoteValues').insertOne({ + doc: Long.fromNumber(10), + int: 10, + double: 2.2222, + array: [[Long.fromNumber(10)]] }); + + const doc = await db.collection('shouldCorrectlyHonorPromoteValues').findOne(); + expect(Long.fromNumber(10)).deep.equals(doc.doc); + expect(new Int32(10)).deep.equals(doc.int); + expect(new Double(2.2222)).deep.equals(doc.double); + + await client.close(); } }); @@ -94,35 +75,24 @@ describe('Promote Values', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; + test: async function () { + const configuration = this.configuration; const client = configuration.newClient({}, { promoteValues: false }); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteValues').insert( - { - doc: Long.fromNumber(10), - int: 10, - double: 2.2222, - array: [[Long.fromNumber(10)]] - }, - function (err) { - expect(err).to.not.exist; - - db.collection('shouldCorrectlyHonorPromoteValues') - .find() - .next(function (err, doc) { - expect(err).to.not.exist; - - test.deepEqual(Long.fromNumber(10), doc.doc); - test.deepEqual(new Int32(10), doc.int); - test.deepEqual(new Double(2.2222), doc.double); - - client.close(done); - }); - } - ); + await client.connect(); + const db = client.db(configuration.db); + await db.collection('shouldCorrectlyHonorPromoteValues').insertOne({ + doc: Long.fromNumber(10), + int: 10, + double: 2.2222, + array: [[Long.fromNumber(10)]] }); + + const doc = await db.collection('shouldCorrectlyHonorPromoteValues').find().next(); + expect(doc.doc).to.deep.equal(Long.fromNumber(10)); + expect(doc.int).to.deep.equal(new Int32(10)); + expect(doc.double).to.deep.equal(new Double(2.2222)); + + await client.close(); } }); @@ -133,35 +103,28 @@ describe('Promote Values', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; + test: async function () { + const configuration = this.configuration; const client = configuration.newClient(); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteValues').insert( - { - doc: Long.fromNumber(10), - int: 10, - double: 2.2222, - array: [[Long.fromNumber(10)]] - }, - function (err) { - expect(err).to.not.exist; - - db.collection('shouldCorrectlyHonorPromoteValues') - .find({}, { promoteValues: false }) - .next(function (err, doc) { - expect(err).to.not.exist; - - test.deepEqual(Long.fromNumber(10), doc.doc); - test.deepEqual(new Int32(10), doc.int); - test.deepEqual(new Double(2.2222), doc.double); - - client.close(done); - }); - } - ); + await client.connect(); + const db = client.db(configuration.db); + + await db.collection('shouldCorrectlyHonorPromoteValues').insertOne({ + doc: Long.fromNumber(10), + int: 10, + double: 2.2222, + array: [[Long.fromNumber(10)]] }); + + const doc = await db + .collection('shouldCorrectlyHonorPromoteValues') + .find({}, { promoteValues: false }) + .next(); + expect(doc.doc).to.deep.equal(Long.fromNumber(10)); + expect(doc.int).to.deep.equal(new Int32(10)); + expect(doc.double).to.deep.equal(new Double(2.2222)); + + await client.close(); } }); @@ -172,35 +135,26 @@ describe('Promote Values', function () { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } }, - test: function (done) { - var configuration = this.configuration; + test: async function () { + const configuration = this.configuration; const client = configuration.newClient(); - client.connect(function (err, client) { - var db = client.db(configuration.db); - db.collection('shouldCorrectlyHonorPromoteValues2').insert( - { - doc: Long.fromNumber(10), - int: 10, - double: 2.2222, - array: [[Long.fromNumber(10)]] - }, - function (err) { - expect(err).to.not.exist; - - db.collection('shouldCorrectlyHonorPromoteValues2') - .aggregate([{ $match: {} }], { promoteValues: false }) - .next(function (err, doc) { - expect(err).to.not.exist; - - test.deepEqual(Long.fromNumber(10), doc.doc); - test.deepEqual(new Int32(10), doc.int); - test.deepEqual(new Double(2.2222), doc.double); - - client.close(done); - }); - } - ); + await client.connect(); + const db = client.db(configuration.db); + await db.collection('shouldCorrectlyHonorPromoteValues2').insertOne({ + doc: Long.fromNumber(10), + int: 10, + double: 2.2222, + array: [[Long.fromNumber(10)]] }); + const doc = await db + .collection('shouldCorrectlyHonorPromoteValues2') + .aggregate([{ $match: {} }], { promoteValues: false }) + .next(); + expect(doc.doc, Long.fromNumber(10)); + expect(doc.int, new Int32(10)); + expect(doc.double, new Double(2.2222)); + + await client.close(); } }); diff --git a/test/integration/node-specific/bson-options/use_bigint_64.test.ts b/test/integration/node-specific/bson-options/use_bigint_64.test.ts new file mode 100644 index 0000000000..34eaf91e8e --- /dev/null +++ b/test/integration/node-specific/bson-options/use_bigint_64.test.ts @@ -0,0 +1,262 @@ +import { expect } from 'chai'; + +import { BSON, Collection, Db, MongoAPIError, MongoClient, WithId } from '../../../mongodb'; + +describe('useBigInt64 option', function () { + let client: MongoClient; + let db: Db; + let coll: Collection; + + afterEach(async function () { + if (client) { + if (coll) { + await coll.drop().catch(() => null); + } + + if (db) { + await db.dropDatabase().catch(() => null); + } + + await client.close(); + } + }); + + describe('when not provided to client', async function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + }); + + it('is set to driver default (useBigInt64=false)', async function () { + expect(client.s.bsonOptions.useBigInt64).to.exist; + expect(client.s.bsonOptions.useBigInt64).to.be.false; + }); + }); + + describe('when set at client level', function () { + beforeEach(async function () { + client = await this.configuration + .newClient( + {}, + { + useBigInt64: true + } + ) + .connect(); + }); + + it('supercedes driver level', function () { + expect(client.s.bsonOptions.useBigInt64).to.exist; + expect(client.s.bsonOptions.useBigInt64).to.be.true; + }); + }); + + describe('when set at DB level', function () { + beforeEach(async function () { + client = await this.configuration + .newClient( + {}, + { + useBigInt64: false + } + ) + .connect(); + + db = client.db(this.configuration.db, { useBigInt64: true }); + }); + + it('supercedes client level', async function () { + expect(db.s.bsonOptions.useBigInt64).to.exist; + expect(db.s.bsonOptions.useBigInt64).to.be.true; + }); + }); + + describe('when set at collection level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + + db = client.db(this.configuration.db, { useBigInt64: false }); + await db.dropCollection('useBigInt64Test').catch(() => null); + coll = await db.createCollection('useBigInt64Test', { useBigInt64: true }); + }); + + it('supercedes db level', function () { + expect(coll.s.bsonOptions.useBigInt64).to.exist; + expect(coll.s.bsonOptions.useBigInt64).to.be.true; + }); + }); + + describe('when set to true at collection level', async function () { + let res: WithId | null; + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db(this.configuration.db); + await db.dropCollection('useBigInt64Test').catch(() => null); + }); + + it('supercedes collection level when set to false at operation level', async function () { + coll = await db.createCollection('useBigInt64Test', { useBigInt64: true }); + await coll.insertMany([{ a: 1n }, { a: 2n }, { a: 3n }, { a: 4n }]); + res = await coll.findOne({}, { useBigInt64: false }); + + expect(res).to.exist; + expect(typeof res?.a).to.equal('number'); + }); + }); + + describe('when set to false at collection level', async function () { + let res: WithId | null; + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db(this.configuration.db); + await db.dropCollection('useBigInt64Test').catch(() => null); + }); + + it('supercedes collection level when set to true at operation level', async function () { + coll = await db.createCollection('useBigInt64Test', { useBigInt64: false }); + await coll.insertMany([{ a: 1n }, { a: 2n }, { a: 3n }, { a: 4n }]); + res = await coll.findOne({}, { useBigInt64: true }); + + expect(res).to.exist; + expect(typeof res?.a).to.equal('bigint'); + }); + }); + + describe('when set to true', function () { + let res: WithId | null; + + beforeEach(async function () { + client = await this.configuration.newClient({}, { useBigInt64: true }).connect(); + + db = client.db(this.configuration.db); + await db.dropCollection('useBigInt64Test').catch(() => null); + + coll = await db.createCollection('useBigInt64Test'); + await coll.insertOne({ a: new BSON.Long(1) }); + + res = await coll.findOne({ a: 1n }); + }); + + it('deserializes Long to bigint', async function () { + expect(res).to.exist; + expect(typeof res?.a).to.equal('bigint'); + expect(res?.a).to.equal(1n); + }); + }); + + describe('when useBigInt64=true and promoteLongs=false', function () { + describe('when set at client level', function () { + it('throws a MongoAPIError', async function () { + expect(() => { + client = this.configuration.newClient( + {}, + { + useBigInt64: true, + promoteLongs: false + } + ); + }).to.throw(MongoAPIError, /Must request either bigint or Long for int64 deserialization/); + }); + }); + + describe('when set at DB level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db('bsonOptions', { promoteLongs: false, useBigInt64: true }); + }); + + it('throws a BSONError', async function () { + const e = await db.createCollection('bsonError').catch(e => e); + expect(e).to.be.instanceOf(BSON.BSONError); + }); + }); + + describe('when set at collection level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db('bsonOptions'); + }); + + it('throws a BSONError', async function () { + const e = await db + .createCollection('bsonError', { promoteLongs: false, useBigInt64: true }) + .catch(e => e); + expect(e).to.be.instanceOf(BSON.BSONError); + }); + }); + + describe('when set at the operation level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + + db = client.db('bsonOptions'); + coll = db.collection('bsonError'); + }); + + it('throws a BSONError', async function () { + const e = await coll + .insertOne({ a: 10n }, { promoteLongs: false, useBigInt64: true }) + .catch(e => e); + + expect(e).to.be.instanceOf(BSON.BSONError); + }); + }); + }); + + describe('when useBigInt64=true and promoteValues=false', function () { + describe('when set at client level', function () { + it('throws a MongoAPIError', async function () { + expect(() => { + client = this.configuration.newClient( + {}, + { + useBigInt64: true, + promoteValues: false + } + ); + }).to.throw(MongoAPIError, /Must request either bigint or Long for int64 deserialization/); + }); + }); + + describe('when set at DB level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db('bsonOptions', { promoteValues: false, useBigInt64: true }); + }); + + it('throws a BSONError', async function () { + const e = await db.createCollection('bsonError').catch(e => e); + expect(e).to.be.instanceOf(BSON.BSONError); + }); + }); + + describe('when set at collection level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db('bsonOptions'); + }); + + it('throws a BSONError', async function () { + const e = await db + .createCollection('bsonError', { promoteValues: false, useBigInt64: true }) + .catch(e => e); + expect(e).to.be.instanceOf(BSON.BSONError); + }); + }); + + describe('when set at the operation level', function () { + beforeEach(async function () { + client = await this.configuration.newClient().connect(); + db = client.db('bsonOptions'); + coll = db.collection('bsonError'); + }); + + it('throws a BSONError', async function () { + const e = await coll + .insertOne({ a: 10n }, { promoteValues: false, useBigInt64: true }) + .catch(e => e); + + expect(e).to.be.instanceOf(BSON.BSONError); + }); + }); + }); +}); diff --git a/test/tools/unified-spec-runner/match.ts b/test/tools/unified-spec-runner/match.ts index c43c3cdf74..66c7e32051 100644 --- a/test/tools/unified-spec-runner/match.ts +++ b/test/tools/unified-spec-runner/match.ts @@ -131,7 +131,10 @@ TYPE_MAP.set( ); TYPE_MAP.set( 'long', - actual => (typeof actual === 'number' && Number.isInteger(actual)) || Long.isLong(actual) + actual => + (typeof actual === 'number' && Number.isInteger(actual)) || + Long.isLong(actual) || + typeof actual === 'bigint' ); /** diff --git a/test/types/bson.test-d.ts b/test/types/bson.test-d.ts index c3698aaff1..b011e9369c 100644 --- a/test/types/bson.test-d.ts +++ b/test/types/bson.test-d.ts @@ -7,6 +7,7 @@ const options: BSONSerializeOptions = {}; expectType(options.checkKeys); expectType(options.serializeFunctions); expectType(options.ignoreUndefined); +expectType(options.useBigInt64); expectType(options.promoteLongs); expectType(options.promoteBuffers); expectType(options.promoteValues); @@ -17,6 +18,7 @@ type PermittedBSONOptionKeys = | 'checkKeys' | 'serializeFunctions' | 'ignoreUndefined' + | 'useBigInt64' | 'promoteLongs' | 'promoteBuffers' | 'promoteValues' diff --git a/test/unit/sessions.test.js b/test/unit/sessions.test.js index 2b8ff043b7..d61e186113 100644 --- a/test/unit/sessions.test.js +++ b/test/unit/sessions.test.js @@ -3,7 +3,13 @@ const mock = require('../tools/mongodb-mock/index'); const { expect } = require('chai'); const { genClusterTime } = require('../tools/common'); -const { ServerSessionPool, ServerSession, ClientSession, applySession } = require('../mongodb'); +const { + ServerSessionPool, + ServerSession, + ClientSession, + applySession, + BSON +} = require('../mongodb'); const { now, isHello } = require('../mongodb'); const { getSymbolFrom } = require('../tools/utils'); const { Long } = require('../mongodb'); @@ -110,6 +116,16 @@ describe('Sessions - unit', function () { expect(session).property('clusterTime').to.equal(alsoValidTime); }); + it('sets clusterTime to the one provided when the signature.keyId is a bigint', () => { + const validClusterTime = { + clusterTime: new BSON.Timestamp(BSON.Long.fromNumber(1, true)), + signature: { hash: new BSON.Binary('test'), keyId: 100n } + }; + + session.advanceClusterTime(validClusterTime); + expect(session.clusterTime.signature.keyId).to.equal(100n); + }); + it('should set the session clusterTime to the one provided if it is greater than the the existing session clusterTime', () => { const validInitialTime = genClusterTime(100); const validGreaterTime = genClusterTime(200);