Skip to content

Commit

Permalink
Merge pull request #15061 from mongodb-js/NODE-6504/double
Browse files Browse the repository at this point in the history
feat(NODE-6504): Add Double as a SchemaType
  • Loading branch information
vkarpov15 authored Dec 9, 2024
2 parents 6d8a0f2 + 19309fe commit 7d25b48
Show file tree
Hide file tree
Showing 13 changed files with 766 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ The permitted SchemaTypes are:
* [Decimal128](api/mongoose.html#mongoose_Mongoose-Decimal128)
* [Map](schematypes.html#maps)
* [UUID](schematypes.html#uuid)
* [Double](schematypes.html#double)
* [Int32](schematypes.html#int32)

Read more about [SchemaTypes here](schematypes.html).
Expand Down
35 changes: 34 additions & 1 deletion docs/schematypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Check out [Mongoose's plugins search](http://plugins.mongoosejs.io) to find plug
* [Schema](#schemas)
* [UUID](#uuid)
* [BigInt](#bigint)
* [Double](#double)
* [Int32](#int32)

### Example
Expand All @@ -69,6 +70,7 @@ const schema = new Schema({
mixed: Schema.Types.Mixed,
_someId: Schema.Types.ObjectId,
decimal: Schema.Types.Decimal128,
double: Schema.Types.Double,
int32bit: Schema.Types.Int32,
array: [],
ofString: [String],
Expand Down Expand Up @@ -649,6 +651,37 @@ const question = new Question({ answer: 42n });
typeof question.answer; // 'bigint'
```

### Double {#double}

Mongoose supports [64-bit IEEE 754-2008 floating point numbers](https://en.wikipedia.org/wiki/IEEE_754-2008_revision) as a SchemaType.
Int32s are stored as [BSON type "double" in MongoDB](https://www.mongodb.com/docs/manual/reference/bson-types/).

```javascript
const studentsSchema = new Schema({
id: Int32
});
const Student = mongoose.model('Student', schema);

const student = new Temperature({ celsius: 1339 });
typeof student.id; // 'number'
```

There are several types of values that will be successfully cast to a Double.

```javascript
new Temperature({ celsius: '1.2e12' }).celsius; // 15 as a Double
new Temperature({ celsius: true }).celsius; // 1 as a Double
new Temperature({ celsius: false }).celsius; // 0 as a Double
new Temperature({ celsius: { valueOf: () => 83.0033 } }).celsius; // 83 as a Double
new Temperature({ celsius: '' }).celsius; // null as a Double
```

The following inputs will result will all result in a [CastError](validation.html#cast-errors) once validated, meaning that it will not throw on initialization, only when validated:

* strings that do not represent a numeric string, a NaN or a null-ish value
* objects that don't have a `valueOf()` function
* an input that represents a value outside the bounds of a IEEE 754-2008 floating point

### Int32 {#int32}

Mongoose supports 32-bit integers as a SchemaType.
Expand All @@ -660,7 +693,7 @@ const studentsSchema = new Schema({
});
const Student = mongoose.model('Student', schema);

const student = new Student({ id: 1339 });
const student = new Temperature({ celsius: 1339 });
typeof student.id; // 'number'
```

Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module.exports.Decimal128 = mongoose.Decimal128;
module.exports.Mixed = mongoose.Mixed;
module.exports.Date = mongoose.Date;
module.exports.Number = mongoose.Number;
module.exports.Double = mongoose.Double;
module.exports.Error = mongoose.Error;
module.exports.MongooseError = mongoose.MongooseError;
module.exports.now = mongoose.now;
Expand Down
50 changes: 50 additions & 0 deletions lib/cast/double.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

const assert = require('assert');
const BSON = require('bson');
const isBsonType = require('../helpers/isBsonType');

/**
* Given a value, cast it to a IEEE 754-2008 floating point, or throw an `Error` if the value
* cannot be casted. `null`, `undefined`, and `NaN` are considered valid inputs.
*
* @param {Any} value
* @return {Number}
* @throws {Error} if `value` does not represent a IEEE 754-2008 floating point. If casting from a string, see [BSON Double.fromString API documentation](https://mongodb.github.io/node-mongodb-native/Next/classes/BSON.Double.html#fromString)
* @api private
*/

module.exports = function castDouble(val) {
if (val == null || val === '') {
return null;
}

let coercedVal;
if (isBsonType(val, 'Long')) {
coercedVal = val.toNumber();
} else if (typeof val === 'string') {
try {
coercedVal = BSON.Double.fromString(val);
return coercedVal;
} catch {
assert.ok(false);
}
} else if (typeof val === 'object') {
const tempVal = val.valueOf() ?? val.toString();
// ex: { a: 'im an object, valueOf: () => 'helloworld' } // throw an error
if (typeof tempVal === 'string') {
try {
coercedVal = BSON.Double.fromString(val);
return coercedVal;
} catch {
assert.ok(false);
}
} else {
coercedVal = Number(tempVal);
}
} else {
coercedVal = Number(val);
}

return new BSON.Double(coercedVal);
};
5 changes: 5 additions & 0 deletions lib/helpers/clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const isObject = require('./isObject');
const isPOJO = require('./isPOJO');
const symbols = require('./symbols');
const trustedSymbol = require('./query/trusted').trustedSymbol;
const BSON = require('bson');

/**
* Object clone with Mongoose natives support.
Expand All @@ -30,6 +31,10 @@ function clone(obj, options, isArrayChild) {
if (obj == null) {
return obj;
}

if (isBsonType(obj, 'Double')) {
return new BSON.Double(obj.value);
}
if (typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean' || typeof obj === 'bigint') {
return obj;
}
Expand Down
1 change: 1 addition & 0 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2892,6 +2892,7 @@ module.exports = exports = Schema;
* - [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed)
* - [UUID](https://mongoosejs.com/docs/schematypes.html#uuid)
* - [BigInt](https://mongoosejs.com/docs/schematypes.html#bigint)
* - [Double] (https://mongoosejs.com/docs/schematypes.html#double)
* - [Int32](https://mongoosejs.com/docs/schematypes.html#int32)
*
* Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
Expand Down
212 changes: 212 additions & 0 deletions lib/schema/double.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
'use strict';

/*!
* Module dependencies.
*/

const CastError = require('../error/cast');
const SchemaType = require('../schemaType');
const castDouble = require('../cast/double');

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

function SchemaDouble(path, options) {
SchemaType.call(this, path, options, 'Double');
}

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

SchemaDouble.defaultOptions = {};

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

/*!
* ignore
*/

SchemaDouble._cast = castDouble;

/**
* Sets a default option for all Double instances.
*
* #### Example:
*
* // Make all Double fields required by default
* mongoose.Schema.Double.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
*/

SchemaDouble.set = SchemaType.set;

SchemaDouble.setters = [];

/**
* Attaches a getter for all Double instances
*
* #### Example:
*
* // Converts Double to be a represent milliseconds upon access
* mongoose.Schema.Double.get(v => v == null ? '0.000 ms' : v.toString() + ' ms');
*
* @param {Function} getter
* @return {this}
* @function get
* @static
* @api public
*/

SchemaDouble.get = SchemaType.get;

/*!
* ignore
*/

SchemaDouble._defaultCaster = v => {
if (v != null) {
if (v._bsontype !== 'Double') {
throw new Error();
}
}

return v;
};

/**
* Get/set the function used to cast arbitrary values to IEEE 754-2008 floating points
*
* #### Example:
*
* // Make Mongoose cast any NaNs to 0
* const defaultCast = mongoose.Schema.Types.Double.cast();
* mongoose.Schema.Types.Double.cast(v => {
* if (isNaN(v)) {
* return 0;
* }
* return defaultCast(v);
* });
*
* // Or disable casting for Doubles entirely (only JS numbers are permitted)
* mongoose.Schema.Double.cast(false);
*
*
* @param {Function} caster
* @return {Function}
* @function get
* @static
* @api public
*/

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

this._cast = caster;

return this._cast;
};


/*!
* ignore
*/

SchemaDouble._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
*/

SchemaDouble.checkRequired = SchemaType.checkRequired;

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

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

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

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

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

/*!
* ignore
*/

function handleSingle(val) {
return this.cast(val);
}

SchemaDouble.prototype.$conditionalHandlers = {
...SchemaType.prototype.$conditionalHandlers,
$gt: handleSingle,
$gte: handleSingle,
$lt: handleSingle,
$lte: handleSingle
};


/*!
* Module exports.
*/

module.exports = SchemaDouble;
1 change: 1 addition & 0 deletions lib/schema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exports.ObjectId = require('./objectId');
exports.String = require('./string');
exports.Subdocument = require('./subdocument');
exports.UUID = require('./uuid');
exports.Double = require('./double');
exports.Int32 = require('./int32');

// alias
Expand Down
Loading

0 comments on commit 7d25b48

Please sign in to comment.