Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NODE-4871): Add support for int64 deserialization to BigInt #542

Merged
merged 20 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,13 @@ Serialize a Javascript object using a predefined Buffer and index into the buffe
| buffer | <code>Buffer</code> | | the buffer containing the serialized set of BSON documents. |
| [options.evalFunctions] | <code>Object</code> | <code>false</code> | evaluate functions in the BSON document scoped to the object deserialized. |
| [options.cacheFunctions] | <code>Object</code> | <code>false</code> | cache evaluated functions for reuse. |
| [options.useBigInt64] | <code>Object</code> | <code>false</code> | when deserializing a Long will return a BigInt. |
| [options.promoteLongs] | <code>Object</code> | <code>true</code> | when deserializing a Long will fit it into a Number if it's smaller than 53 bits |
| [options.promoteBuffers] | <code>Object</code> | <code>false</code> | when deserializing a Binary will return it as a node.js Buffer instance. |
| [options.promoteValues] | <code>Object</code> | <code>false</code> | when deserializing will promote BSON values to their Node.js closest equivalent types. |
| [options.fieldsAsRaw] | <code>Object</code> | <code></code> | allow to specify if there what fields we wish to return as unserialized raw buffer. |
| [options.bsonRegExp] | <code>Object</code> | <code>false</code> | return BSON regular expressions as BSONRegExp instances. |
| [options.allowObjectSmallerThanBufferSize] | <code>boolean</code> | <code>false</code> | allows the buffer to be larger than the parsed BSON object |
| [options.allowObjectSmallerThanBufferSize] | <code>boolean</code> | <code>false</code> | allows the buffer to be larger than the parsed BSON object. |

Deserialize data as BSON.

Expand Down
28 changes: 21 additions & 7 deletions src/parser/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import { validateUtf8 } from '../validate_utf8';

/** @public */
export interface DeserializeOptions {
/** when deserializing a Long will fit it into a Number if it's smaller than 53 bits */
/** when deserializing a Long will return as a BigInt. */
useBigInt64?: boolean;
/** when deserializing a Long will fit it into a Number if it's smaller than 53 bits. */
promoteLongs?: boolean;
/** when deserializing a Binary will return it as a node.js Buffer instance. */
promoteBuffers?: boolean;
Expand All @@ -29,7 +31,7 @@ export interface DeserializeOptions {
fieldsAsRaw?: Document;
/** return BSON regular expressions as BSONRegExp instances. */
bsonRegExp?: boolean;
/** allows the buffer to be larger than the parsed BSON object */
/** allows the buffer to be larger than the parsed BSON object. */
allowObjectSmallerThanBufferSize?: boolean;
/** Offset into buffer to begin reading document from */
index?: number;
Expand Down Expand Up @@ -96,7 +98,7 @@ export function deserialize(
);
}

// Start deserializtion
// Start deserialization
return deserializeObject(buffer, index, options, isArray);
}

Expand All @@ -117,9 +119,18 @@ function deserializeObject(
const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false;

// Controls the promotion of values vs wrapper classes
const promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers'];
const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs'];
const promoteValues = options['promoteValues'] == null ? true : options['promoteValues'];
const promoteBuffers = options.promoteBuffers ?? false;
const promoteLongs = options.promoteLongs ?? true;
const promoteValues = options.promoteValues ?? true;
const useBigInt64 = options.useBigInt64 ?? false;

if (useBigInt64 && !promoteValues) {
throw new BSONError('Must either request bigint or Long for int64 deserialization');
}

if (useBigInt64 && !promoteLongs) {
throw new BSONError('Must either request bigint or Longs for int64 deserialization');
}

// Ensures default validation option if none given
const validation = options.validation == null ? { utf8: true } : options.validation;
Expand Down Expand Up @@ -334,8 +345,11 @@ function deserializeObject(
(buffer[index++] << 16) |
(buffer[index++] << 24);
const long = new Long(lowBits, highBits);
if (useBigInt64) {
value = BigInt(lowBits) | (BigInt(highBits) << 32n);
}
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
// Promote the long if possible
if (promoteLongs && promoteValues === true) {
else if (promoteLongs && promoteValues === true) {
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
value =
long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
? long.toNumber()
Expand Down
111 changes: 111 additions & 0 deletions test/node/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { BSON } from '../register-bson';
import { bufferFromHexArray } from './tools/utils';
import { expect } from 'chai';

describe('BSON BigInt deserialization support', function () {
describe('BSON.deserialize()', function () {
let testSerializedDoc: Buffer;
before(function () {
testSerializedDoc = bufferFromHexArray(['12', '6100', '2300000000000000']);
});
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

it('deserializes int64 to Number when useBigInt64,promoteValues, promoteLongs are default', function () {
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
const deserializedDoc = BSON.deserialize(testSerializedDoc);
expect(deserializedDoc).to.deep.equal({ a: 0x23 });
});

it('deserializes int64 to BigInt when useBigInt64 == true, promoteValues, promoteLongs are default', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, { useBigInt64: true });
expect(deserializedDoc).to.deep.equal({ a: 0x23n });
});

it('deserializes int64 to BigInt when useBigInt64 == true and promoteLongs == true', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: true,
promoteLongs: true
});
expect(deserializedDoc).to.deep.equal({ a: 0x23n });
});

it('deserializes int64 to Number when useBigInt64 == false and promoteLongs == true', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: false,
promoteLongs: true
});
expect(deserializedDoc).to.deep.equal({ a: 0x23 });
});

it('deserializes int64 to Number when useBigInt64 == false and promoteLongs is default', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, { useBigInt64: false });
expect(deserializedDoc).to.deep.equal({ a: 0x23 });
});

it('deserializes int64 to BSON.Long when useBigInt64 == false and promoteLongs == false', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: false,
promoteLongs: false
});
expect(deserializedDoc).to.deep.equal({ a: new BSON.Long(0x23) });
});

it('deserializes int64 to BSON.Long when useBigInt64 is default and promoteValues == false', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, { promoteValues: false });
expect(deserializedDoc).to.deep.equal({ a: new BSON.Long(0x23) });
});

it('deserializes int64 to BSON.Long when useBigInt64 == false and promoteValues == false', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: false,
promoteValues: false
});
expect(deserializedDoc).to.deep.equal({ a: new BSON.Long(0x23) });
});

it('deserializes int64 to BSON.Long when useBigInt64 == false, promoteLongs == false, and promoteValues == false', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: false,
promoteLongs: false,
promoteValues: false
});
expect(deserializedDoc).to.deep.equal({ a: new BSON.Long(0x23) });
});

it('deserializes int64 to Number when useBigInt64 == false, promoteLongs == true, and promoteValues == true', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: false,
promoteLongs: true,
promoteValues: true
});
expect(deserializedDoc).to.deep.equal({ a: 0x23 });
});

it('deserializes int64 to Number when useBigInt64 == true, promoteLongs == true, and promoteValues == true', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: true,
promoteLongs: true,
promoteValues: true
});
expect(deserializedDoc).to.deep.equal({ a: 0x23n });
});

it('deserializes int64 to BSON.Long when useBigInt64 == false and promoteLongs == false', function () {
const deserializedDoc = BSON.deserialize(testSerializedDoc, {
useBigInt64: false,
promoteLongs: false
});
expect(deserializedDoc).to.deep.equal({ a: new BSON.Long(0x23) });
});

it('throws error when useBigInt64 == true and promoteLongs == false', function () {
expect(function () {
BSON.deserialize(testSerializedDoc, { useBigInt64: true, promoteLongs: false });
}).to.throw(BSON.BSONError);
});

it('throws error when useBigInt64 == true and promoteValues == false', function () {
expect(function () {
BSON.deserialize(testSerializedDoc, { useBigInt64: true, promoteValues: false });
}).to.throw(BSON.BSONError);
});
});
});