Skip to content

Commit

Permalink
Merge pull request #13318 from Automattic/vkarpov15/gh-13081
Browse files Browse the repository at this point in the history
feat(schema): add BigInt support, upgrade mongodb -> 5.3.0
  • Loading branch information
vkarpov15 committed Apr 25, 2023
2 parents 0b7323b + 4255f56 commit 624ee82
Show file tree
Hide file tree
Showing 11 changed files with 491 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ module.exports = {
},
env: {
node: true,
es6: true
es6: true,
es2020: true
},
rules: {
'comma-style': 'error',
Expand Down
16 changes: 16 additions & 0 deletions docs/schematypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Check out [Mongoose's plugins search](http://plugins.mongoosejs.io) to find plug
- [Map](#maps)
- [Schema](#schemas)
- [UUID](#uuid)
- [BigInt](#bigint)

<h3>Example</h3>

Expand Down Expand Up @@ -632,6 +633,21 @@ const schema = new mongoose.Schema({
});
```

<h3 id="bigint">BigInt</h3>

Mongoose supports [JavaScript BigInts](https://thecodebarbarian.com/an-overview-of-bigint-in-node-js.html) as a SchemaType.
BigInts are stored as [64-bit integers in MongoDB (BSON type "long")](https://www.mongodb.com/docs/manual/reference/bson-types/).

```javascript
const questionSchema = new Schema({
answer: BigInt
});
const Question = mongoose.model('Question', questionSchema);

const question = new Question({ answer: 42n });
typeof question.answer; // 'bigint'
```

<h2 id="getters"><a href="#getters">Getters</a></h2>

Getters are like virtuals for paths defined in your schema. For example,
Expand Down
10 changes: 10 additions & 0 deletions docs/tutorials/lean.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ In this tutorial, you'll learn more about the tradeoffs of using `lean()`.
* [Lean and Populate](#lean-and-populate)
* [When to Use Lean](#when-to-use-lean)
* [Plugins](#plugins)
* [BigInts](#bigints)

<h2 id="using-lean"><a href="#using-lean">Using Lean</a></h2>

Expand Down Expand Up @@ -140,3 +141,12 @@ schema.virtual('lowercase', function() {
this.get('name'); // Crashes because `this` is not a Mongoose document.
});
```

## BigInts

By default, the MongoDB Node driver converts longs stored in MongoDB into JavaScript numbers, **not** [BigInts](https://thecodebarbarian.com/an-overview-of-bigint-in-node-js.html).
Set the `useBigInt64` option on your `lean()` queries to inflate longs into BigInts.

```acquit
[require:Lean Tutorial.*bigint]
```
31 changes: 31 additions & 0 deletions lib/cast/bigint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const assert = require('assert');

/**
* Given a value, cast it to a BigInt, or throw an `Error` if the value
* cannot be casted. `null` and `undefined` are considered valid.
*
* @param {Any} value
* @return {Number}
* @throws {Error} if `value` is not one of the allowed values
* @api private
*/

module.exports = function castBigInt(val) {
if (val == null) {
return val;
}
if (val === '') {
return null;
}
if (typeof val === 'bigint') {
return val;
}

if (typeof val === 'string' || typeof val === 'number') {
return BigInt(val);
}

assert.ok(false);
};
3 changes: 2 additions & 1 deletion lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -1525,12 +1525,13 @@ Query.prototype.getOptions = function() {
* - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): If `timestamps` is set in the schema, set this option to `false` to skip timestamps for that particular update. Has no effect if `timestamps` is not enabled in the schema options.
* - overwriteDiscriminatorKey: allow setting the discriminator key in the update. Will use the correct discriminator schema if the update changes the discriminator key.
*
* The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`:
* The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, `findOneAndReplace()`, `findOneAndDelete()`, and `findByIdAndUpdate()`:
*
* - [lean](#query_Query-lean)
* - [populate](/docs/populate.html)
* - [projection](#query_Query-projection)
* - sanitizeProjection
* - useBigInt64
*
* The following options are only for all operations **except** `updateOne()`, `updateMany()`, `deleteOne()`, and `deleteMany()`:
*
Expand Down
2 changes: 2 additions & 0 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2665,6 +2665,8 @@ module.exports = exports = Schema;
* - [Date](/docs/schematypes.html#dates)
* - [ObjectId](/docs/schematypes.html#objectids) | Oid
* - [Mixed](/docs/schematypes.html#mixed)
* - [UUID](/docs/schematypes.html#uuid)
* - [BigInt](/docs/schematypes.html#bigint)
*
* Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
*
Expand Down
221 changes: 221 additions & 0 deletions lib/schema/bigint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
'use strict';

/*!
* Module dependencies.
*/

const CastError = require('../error/cast');
const SchemaType = require('../schematype');
const castBigInt = require('../cast/bigint');
const utils = require('../utils');

/**
* BigInt SchemaType constructor.
*
* @param {String} path
* @param {Object} options
* @inherits SchemaType
* @api public
*/

function SchemaBigInt(path, options) {
SchemaType.call(this, path, options, 'BigInt');
}

/**
* This schema type's name, to defend against minifiers that mangle
* function names.
*
* @api public
*/
SchemaBigInt.schemaName = 'BigInt';

SchemaBigInt.defaultOptions = {};

/*!
* Inherits from SchemaType.
*/
SchemaBigInt.prototype = Object.create(SchemaType.prototype);
SchemaBigInt.prototype.constructor = SchemaBigInt;

/*!
* ignore
*/

SchemaBigInt._cast = castBigInt;

/**
* Sets a default option for all BigInt instances.
*
* #### Example:
*
* // Make all bigints required by default
* mongoose.Schema.BigInt.set('required', true);
*
* @param {String} option The option you'd like to set the value for
* @param {Any} value value for option
* @return {undefined}
* @function set
* @static
* @api public
*/

SchemaBigInt.set = SchemaType.set;

/**
* Get/set the function used to cast arbitrary values to booleans.
*
* #### Example:
*
* // Make Mongoose cast empty string '' to false.
* const original = mongoose.Schema.BigInt.cast();
* mongoose.Schema.BigInt.cast(v => {
* if (v === '') {
* return false;
* }
* return original(v);
* });
*
* // Or disable casting entirely
* mongoose.Schema.BigInt.cast(false);
*
* @param {Function} caster
* @return {Function}
* @function get
* @static
* @api public
*/

SchemaBigInt.cast = function cast(caster) {
if (arguments.length === 0) {
return this._cast;
}
if (caster === false) {
caster = this._defaultCaster;
}
this._cast = caster;

return this._cast;
};

/*!
* ignore
*/

SchemaBigInt._checkRequired = v => v != null;

/**
* Override the function the required validator uses to check whether a value
* passes the `required` check.
*
* @param {Function} fn
* @return {Function}
* @function checkRequired
* @static
* @api public
*/

SchemaBigInt.checkRequired = SchemaType.checkRequired;

/**
* Check if the given value satisfies a required validator.
*
* @param {Any} value
* @return {Boolean}
* @api public
*/

SchemaBigInt.prototype.checkRequired = function(value) {
return this.constructor._checkRequired(value);
};

/**
* Casts to bigint
*
* @param {Object} value
* @param {Object} model this value is optional
* @api private
*/

SchemaBigInt.prototype.cast = function(value) {
let castBigInt;
if (typeof this._castFunction === 'function') {
castBigInt = this._castFunction;
} else if (typeof this.constructor.cast === 'function') {
castBigInt = this.constructor.cast();
} else {
castBigInt = SchemaBigInt.cast();
}

try {
return castBigInt(value);
} catch (error) {
throw new CastError('BigInt', value, this.path, error, this);
}
};

/*!
* ignore
*/

SchemaBigInt.$conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, {
$gt: handleSingle,
$gte: handleSingle,
$lt: handleSingle,
$lte: handleSingle
});

/*!
* ignore
*/

function handleSingle(val, context) {
return this.castForQuery(null, val, context);
}

/**
* Casts contents for queries.
*
* @param {String} $conditional
* @param {any} val
* @api private
*/

SchemaBigInt.prototype.castForQuery = function($conditional, val, context) {
let handler;
if ($conditional != null) {
handler = SchemaBigInt.$conditionalHandlers[$conditional];

if (handler) {
return handler.call(this, val);
}

return this.applySetters(null, val, context);
}

return this.applySetters(val, context);
};

/**
*
* @api private
*/

SchemaBigInt.prototype._castNullish = function _castNullish(v) {
if (typeof v === 'undefined') {
return v;
}
const castBigInt = typeof this.constructor.cast === 'function' ?
this.constructor.cast() :
SchemaBigInt.cast();
if (castBigInt == null) {
return v;
}
return v;
};

/*!
* Module exports.
*/

module.exports = SchemaBigInt;
27 changes: 8 additions & 19 deletions lib/schema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,19 @@

'use strict';

exports.String = require('./string');

exports.Number = require('./number');

exports.Boolean = require('./boolean');

exports.DocumentArray = require('./documentarray');

exports.Subdocument = require('./SubdocumentPath');

exports.Array = require('./array');

exports.Boolean = require('./boolean');
exports.BigInt = require('./bigint');
exports.Buffer = require('./buffer');

exports.Date = require('./date');

exports.ObjectId = require('./objectid');

exports.Mixed = require('./mixed');

exports.Decimal128 = exports.Decimal = require('./decimal128');

exports.DocumentArray = require('./documentarray');
exports.Map = require('./map');

exports.Mixed = require('./mixed');
exports.Number = require('./number');
exports.ObjectId = require('./objectid');
exports.String = require('./string');
exports.Subdocument = require('./SubdocumentPath');
exports.UUID = require('./uuid');

// alias
Expand Down
Loading

0 comments on commit 624ee82

Please sign in to comment.