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

fix(NODE-2944): Reintroduce bson-ext support #2823

Merged
merged 10 commits into from
Jun 3, 2021
22 changes: 22 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,16 @@ functions:
rm -f ./prepare_client_encryption.sh

MONGODB_URI="${MONGODB_URI}" bash ${PROJECT_DIRECTORY}/.evergreen/run-custom-csfle-tests.sh
run bson-ext test:
- command: shell.exec
type: test
params:
working_dir: src
timeout_secs: 60
script: |
${PREPARE_SHELL}

MONGODB_URI="${MONGODB_URI}" bash ${PROJECT_DIRECTORY}/.evergreen/run-bson-ext-test.sh
upload test results:
- command: attach.xunit_results
params:
Expand Down Expand Up @@ -1353,6 +1363,18 @@ tasks:
VERSION: '4.4'
TOPOLOGY: server
- func: run custom csfle tests
- name: run-bson-ext-test
tags:
- run-bson-ext-test
commands:
- func: install dependencies
vars:
NODE_LTS_NAME: erbium
- func: bootstrap mongo-orchestration
vars:
VERSION: '4.4'
TOPOLOGY: server
- func: run bson-ext test
buildvariants:
- name: macos-1014-dubnium
display_name: macOS 10.14 Node Dubnium
Expand Down
11 changes: 11 additions & 0 deletions .evergreen/config.yml.in
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,17 @@ functions:

MONGODB_URI="${MONGODB_URI}" bash ${PROJECT_DIRECTORY}/.evergreen/run-custom-csfle-tests.sh

"run bson-ext test":
- command: shell.exec
type: test
params:
working_dir: "src"
timeout_secs: 60
script: |
${PREPARE_SHELL}

MONGODB_URI="${MONGODB_URI}" bash ${PROJECT_DIRECTORY}/.evergreen/run-bson-ext-test.sh

"upload test results":
# Upload the xunit-format test results.
- command: attach.xunit_results
Expand Down
22 changes: 22 additions & 0 deletions .evergreen/generate_evergreen_tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,28 @@ SINGLETON_TASKS.push({
]
});

// special case for custom BSON-ext test
SINGLETON_TASKS.push({
name: 'run-bson-ext-test',
tags: ['run-bson-ext-test'],
commands: [
{
func: 'install dependencies',
vars: {
NODE_LTS_NAME: 'erbium',
},
},
{
func: 'bootstrap mongo-orchestration',
vars: {
VERSION: '4.4',
TOPOLOGY: 'server'
}
},
{ func: 'run bson-ext test' }
]
});

const fileData = yaml.safeLoad(fs.readFileSync(`${__dirname}/config.yml.in`, 'utf8'));
fileData.tasks = (fileData.tasks || []).concat(BASE_TASKS).concat(TASKS).concat(SINGLETON_TASKS);
fileData.buildvariants = (fileData.buildvariants || []).concat(BUILD_VARIANTS);
Expand Down
61 changes: 30 additions & 31 deletions src/bson.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
// import type * as _BSON from 'bson';
// let BSON: typeof _BSON = require('bson');
// try {
// BSON = require('bson-ext');
// } catch {} // eslint-disable-line
import type {
serialize as serializeFn,
deserialize as deserializeFn,
calculateObjectSize as calculateObjectSizeFn
} from 'bson';

// eslint-disable-next-line @typescript-eslint/no-var-requires
let BSON = require('bson');
try {
BSON = require('bson-ext');
} catch {} // eslint-disable-line

// export = BSON;
/** @internal */
export const deserialize = BSON.deserialize as typeof deserializeFn;
/** @internal */
export const serialize = BSON.serialize as typeof serializeFn;
/** @internal */
export const calculateObjectSize = BSON.calculateObjectSize as typeof calculateObjectSizeFn;

export {
Long,
Expand All @@ -21,38 +32,26 @@ export {
BSONRegExp,
BSONSymbol,
Map,
deserialize,
serialize,
calculateObjectSize
Document
} from 'bson';

/** @public */
export interface Document {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
import type { DeserializeOptions, SerializeOptions } from 'bson';

import type { SerializeOptions } from 'bson';

// TODO: Remove me when types from BSON are updated
/**
* BSON Serialization options.
* @public
*/
export interface BSONSerializeOptions extends Omit<SerializeOptions, 'index'> {
/** Return document results as raw BSON buffers */
fieldsAsRaw?: { [key: string]: boolean };
/** Promotes BSON values to native types where possible, set to false to only receive wrapper types */
promoteValues?: boolean;
/** Promotes Binary BSON values to native Node Buffers */
promoteBuffers?: boolean;
/** Promotes long values to number if they fit inside the 53 bits resolution */
promoteLongs?: boolean;
/** Serialize functions on any object */
serializeFunctions?: boolean;
/** Specify if the BSON serializer should ignore undefined fields */
ignoreUndefined?: boolean;

export interface BSONSerializeOptions
extends Omit<SerializeOptions, 'index'>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we discovered that Serialize and Deserialize options end up being effectively the same here, so this code can be simplified accordingly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked over this too quickly in our discussion, there is a difference and these keys below are the ones we desire from the underlying BSON library:

serialize options: checkKeys serializeFunctions ignoreUndefined
deserialize options: promoteLongs promoteBuffers promoteValues fieldsAsRaw

I added a typescript test to cover this expectation.

Omit<
DeserializeOptions,
| 'evalFunctions'
| 'cacheFunctions'
| 'cacheFunctionsCrc32'
| 'bsonRegExp'
| 'allowObjectSmallerThanBufferSize'
> {
/** Return BSON filled buffers from operations */
raw?: boolean;
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,4 @@ export type {
MetaProjectionOperators,
MetaSortOperators
} from './mongo_types';
export type { serialize, deserialize } from './bson';
7 changes: 4 additions & 3 deletions test/benchmarks/driverBench/common.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable no-restricted-modules */
'use strict';

const fs = require('fs');
const path = require('path');
const { MongoClient } = require('../../../src/mongo_client');
const { GridFsBucket } = require('../../../src/gridfs-stream');
const { MongoClient } = require('../../..');
const { GridFSBucket } = require('../../..');

const DB_NAME = 'perftest';
const COLLECTION_NAME = 'corpus';
Expand Down Expand Up @@ -54,7 +55,7 @@ function dropCollection() {
}

function initBucket() {
this.bucket = new GridFsBucket(this.db);
this.bucket = new GridFSBucket(this.db);
}

function dropBucket() {
Expand Down
13 changes: 11 additions & 2 deletions test/benchmarks/driverBench/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ const MongoBench = require('../mongoBench');
const Runner = MongoBench.Runner;
const commonHelpers = require('./common');

const BSON = require('bson');
let BSON = require('bson');
try {
BSON = require('bson-ext');
} catch (_) {
// do not care
}

const { EJSON } = require('bson');

const makeClient = commonHelpers.makeClient;
Expand Down Expand Up @@ -359,5 +365,8 @@ benchmarkRunner
driverBench
};
})
.then(data => console.log(data))
.then(data => {
data.bsonType = BSON.serialize.toString().includes('native code') ? 'bson-ext' : 'js-bson';
console.log(data);
})
.catch(err => console.error(err));
49 changes: 49 additions & 0 deletions test/unit/bson_import.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

const { expect } = require('chai');
const BSON = require('../../src/bson');

describe('BSON Library Import', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we discussed organizing the tests here a little differently: using require.resolve instead of require, and splitting the tests into 2 describe blocks - one for bson-ext and one for js-bson, each of which should have one it block for 'should import the correct library' and another it block for 'should correctly roundtrip supported data types'; using helpers and/or a data table structure should help minimize code duplication

it('should import bson-ext if it exists', function () {
try {
require('bson-ext');
} catch (_) {
this.skip();
}
expect(BSON.deserialize).to.be.a('function');
expect(BSON.serialize).to.be.a('function');
expect(BSON.calculateObjectSize).to.be.a('function');
// Confirms we are using the bson-ext library
expect(BSON.deserialize.toString()).to.include('[native code]');
expect(BSON.serialize.toString()).to.include('[native code]');
expect(BSON.calculateObjectSize.toString()).to.include('[native code]');
});

it('bson-ext should correctly round trip a Long', function () {
try {
require('bson-ext');
} catch (_) {
this.skip();
}

const longValue = BSON.Long.fromNumber(2);

const roundTrip = BSON.deserialize(BSON.serialize({ longValue }));

expect(roundTrip).has.property('longValue');
});
it('should import js-bson if bson-ext does not exist', function () {
try {
require('bson-ext');
this.skip();
// eslint-disable-next-line no-empty
} catch (_) {}
expect(BSON.deserialize).to.be.a('function');
expect(BSON.serialize).to.be.a('function');
expect(BSON.calculateObjectSize).to.be.a('function');

expect(BSON.deserialize.toString()).to.not.include('[native code]');
expect(BSON.serialize.toString()).to.not.include('[native code]');
expect(BSON.calculateObjectSize.toString()).to.not.include('[native code]');
});
});