lib/data-types.js
'use strict';
const util = require('util');
const _ = require('lodash');
const wkx = require('wkx');
const sequelizeErrors = require('./errors');
const Validator = require('./utils/validator-extras').validator;
const momentTz = require('moment-timezone');
const moment = require('moment');
const { logger } = require('./utils/logger');
const warnings = {};
const { classToInvokable } = require('./utils/classToInvokable');
class ABSTRACT {
toString(options) {
return this.toSql(options);
}
toSql() {
return this.key;
}
stringify(value, options) {
if (this._stringify) {
return this._stringify(value, options);
}
return value;
}
bindParam(value, options) {
if (this._bindParam) {
return this._bindParam(value, options);
}
return options.bindParam(this.stringify(value, options));
}
static toString() {
return this.name;
}
static warn(link, text) {
if (!warnings[text]) {
warnings[text] = true;
logger.warn(`${text} \n>> Check: ${link}`);
}
}
static extend(oldType) {
return new this(oldType.options);
}
}
ABSTRACT.prototype.dialectTypes = '';
/**
* STRING A variable length string
*/
class STRING extends ABSTRACT {
/**
* @param {number} [length=255] length of string
* @param {boolean} [binary=false] Is this binary?
*/
constructor(length, binary) {
super();
const options = typeof length === 'object' && length || { length, binary };
this.options = options;
this._binary = options.binary;
this._length = options.length || 255;
}
toSql() {
return `VARCHAR(${this._length})${this._binary ? ' BINARY' : ''}`;
}
validate(value) {
if (Object.prototype.toString.call(value) !== '[object String]') {
if (this.options.binary && Buffer.isBuffer(value) || typeof value === 'number') {
return true;
}
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value));
}
return true;
}
get BINARY() {
this._binary = true;
this.options.binary = true;
return this;
}
static get BINARY() {
return new this().BINARY;
}
}
/**
* CHAR A fixed length string
*/
class CHAR extends STRING {
/**
* @param {number} [length=255] length of string
* @param {boolean} [binary=false] Is this binary?
*/
constructor(length, binary) {
super(typeof length === 'object' && length || { length, binary });
}
toSql() {
return `CHAR(${this._length})${this._binary ? ' BINARY' : ''}`;
}
}
/**
* Unlimited length TEXT column
*/
class TEXT extends ABSTRACT {
/**
* @param {string} [length=''] could be tiny, medium, long.
*/
constructor(length) {
super();
const options = typeof length === 'object' && length || { length };
this.options = options;
this._length = options.length || '';
}
toSql() {
switch (this._length.toLowerCase()) {
case 'tiny':
return 'TINYTEXT';
case 'medium':
return 'MEDIUMTEXT';
case 'long':
return 'LONGTEXT';
default:
return this.key;
}
}
validate(value) {
if (typeof value !== 'string') {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value));
}
return true;
}
}
/**
* An unlimited length case-insensitive text column.
* Original case is preserved but acts case-insensitive when comparing values (such as when finding or unique constraints).
* Only available in Postgres and SQLite.
*
*/
class CITEXT extends ABSTRACT {
toSql() {
return 'CITEXT';
}
validate(value) {
if (typeof value !== 'string') {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value));
}
return true;
}
}
/**
* Base number type which is used to build other types
*/
class NUMBER extends ABSTRACT {
/**
* @param {Object} options type options
* @param {string|number} [options.length] length of type, like `INT(4)`
* @param {boolean} [options.zerofill] Is zero filled?
* @param {boolean} [options.unsigned] Is unsigned?
* @param {string|number} [options.decimals] number of decimal points, used with length `FLOAT(5, 4)`
* @param {string|number} [options.precision] defines precision for decimal type
* @param {string|number} [options.scale] defines scale for decimal type
*/
constructor(options = {}) {
super();
if (typeof options === 'number') {
options = {
length: options
};
}
this.options = options;
this._length = options.length;
this._zerofill = options.zerofill;
this._decimals = options.decimals;
this._precision = options.precision;
this._scale = options.scale;
this._unsigned = options.unsigned;
}
toSql() {
let result = this.key;
if (this._length) {
result += `(${this._length}`;
if (typeof this._decimals === 'number') {
result += `,${this._decimals}`;
}
result += ')';
}
if (this._unsigned) {
result += ' UNSIGNED';
}
if (this._zerofill) {
result += ' ZEROFILL';
}
return result;
}
validate(value) {
if (!Validator.isFloat(String(value))) {
throw new sequelizeErrors.ValidationError(util.format(`%j is not a valid ${this.key.toLowerCase()}`, value));
}
return true;
}
_stringify(number) {
if (typeof number === 'number' || typeof number === 'boolean' || number === null || number === undefined) {
return number;
}
if (typeof number.toString === 'function') {
return number.toString();
}
return number;
}
get UNSIGNED() {
this._unsigned = true;
this.options.unsigned = true;
return this;
}
get ZEROFILL() {
this._zerofill = true;
this.options.zerofill = true;
return this;
}
static get UNSIGNED() {
return new this().UNSIGNED;
}
static get ZEROFILL() {
return new this().ZEROFILL;
}
}
/**
* A 32 bit integer
*/
class INTEGER extends NUMBER {
validate(value) {
if (!Validator.isInt(String(value))) {
throw new sequelizeErrors.ValidationError(util.format(`%j is not a valid ${this.key.toLowerCase()}`, value));
}
return true;
}
}
/**
* A 8 bit integer
*/
class TINYINT extends INTEGER {
}
/**
* A 16 bit integer
*/
class SMALLINT extends INTEGER {
}
/**
* A 24 bit integer
*/
class MEDIUMINT extends INTEGER {
}
/**
* A 64 bit integer
*/
class BIGINT extends INTEGER {
}
/**
* Floating point number (4-byte precision).
*/
class FLOAT extends NUMBER {
/**
* @param {string|number} [length] length of type, like `FLOAT(4)`
* @param {string|number} [decimals] number of decimal points, used with length `FLOAT(5, 4)`
*/
constructor(length, decimals) {
super(typeof length === 'object' && length || { length, decimals });
}
validate(value) {
if (!Validator.isFloat(String(value))) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid float', value));
}
return true;
}
}
/**
* Floating point number (4-byte precision).
*/
class REAL extends NUMBER {
/**
* @param {string|number} [length] length of type, like `REAL(4)`
* @param {string|number} [decimals] number of decimal points, used with length `REAL(5, 4)`
*/
constructor(length, decimals) {
super(typeof length === 'object' && length || { length, decimals });
}
}
/**
* Floating point number (8-byte precision).
*/
class DOUBLE extends NUMBER {
/**
* @param {string|number} [length] length of type, like `DOUBLE PRECISION(25)`
* @param {string|number} [decimals] number of decimal points, used with length `DOUBLE PRECISION(25, 10)`
*/
constructor(length, decimals) {
super(typeof length === 'object' && length || { length, decimals });
}
}
/**
* Decimal type, variable precision, take length as specified by user
*/
class DECIMAL extends NUMBER {
/**
* @param {string|number} [precision] defines precision
* @param {string|number} [scale] defines scale
*/
constructor(precision, scale) {
super(typeof precision === 'object' && precision || { precision, scale });
}
toSql() {
if (this._precision || this._scale) {
return `DECIMAL(${[this._precision, this._scale].filter(_.identity).join(',')})`;
}
return 'DECIMAL';
}
validate(value) {
if (!Validator.isDecimal(String(value))) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid decimal', value));
}
return true;
}
}
// TODO: Create intermediate class
const protoExtensions = {
escape: false,
_value(value) {
if (isNaN(value)) {
return 'NaN';
}
if (!isFinite(value)) {
const sign = value < 0 ? '-' : '';
return `${sign}Infinity`;
}
return value;
},
_stringify(value) {
return `'${this._value(value)}'`;
},
_bindParam(value, options) {
return options.bindParam(this._value(value));
}
};
for (const floating of [FLOAT, DOUBLE, REAL]) {
Object.assign(floating.prototype, protoExtensions);
}
/**
* A boolean / tinyint column, depending on dialect
*/
class BOOLEAN extends ABSTRACT {
toSql() {
return 'TINYINT(1)';
}
validate(value) {
if (!Validator.isBoolean(String(value))) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid boolean', value));
}
return true;
}
_sanitize(value) {
if (value !== null && value !== undefined) {
if (Buffer.isBuffer(value) && value.length === 1) {
// Bit fields are returned as buffers
value = value[0];
}
const type = typeof value;
if (type === 'string') {
// Only take action on valid boolean strings.
return value === 'true' ? true : value === 'false' ? false : value;
}
if (type === 'number') {
// Only take action on valid boolean integers.
return value === 1 ? true : value === 0 ? false : value;
}
}
return value;
}
}
BOOLEAN.parse = BOOLEAN.prototype._sanitize;
/**
* A time column
*
*/
class TIME extends ABSTRACT {
toSql() {
return 'TIME';
}
}
/**
* Date column with timezone, default is UTC
*/
class DATE extends ABSTRACT {
/**
* @param {string|number} [length] precision to allow storing milliseconds
*/
constructor(length) {
super();
const options = typeof length === 'object' && length || { length };
this.options = options;
this._length = options.length || '';
}
toSql() {
return 'DATETIME';
}
validate(value) {
if (!Validator.isDate(String(value))) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid date', value));
}
return true;
}
_sanitize(value, options) {
if ((!options || options && !options.raw) && !(value instanceof Date) && !!value) {
return new Date(value);
}
return value;
}
_isChanged(value, originalValue) {
if (originalValue && !!value &&
(value === originalValue ||
value instanceof Date && originalValue instanceof Date && value.getTime() === originalValue.getTime())) {
return false;
}
// not changed when set to same empty value
if (!originalValue && !value && originalValue === value) {
return false;
}
return true;
}
_applyTimezone(date, options) {
if (options.timezone) {
if (momentTz.tz.zone(options.timezone)) {
return momentTz(date).tz(options.timezone);
}
return date = moment(date).utcOffset(options.timezone);
}
return momentTz(date);
}
_stringify(date, options) {
date = this._applyTimezone(date, options);
// Z here means current timezone, _not_ UTC
return date.format('YYYY-MM-DD HH:mm:ss.SSS Z');
}
}
/**
* A date only column (no timestamp)
*/
class DATEONLY extends ABSTRACT {
toSql() {
return 'DATE';
}
_stringify(date) {
return moment(date).format('YYYY-MM-DD');
}
_sanitize(value, options) {
if ((!options || options && !options.raw) && !!value) {
return moment(value).format('YYYY-MM-DD');
}
return value;
}
_isChanged(value, originalValue) {
if (originalValue && !!value && originalValue === value) {
return false;
}
// not changed when set to same empty value
if (!originalValue && !value && originalValue === value) {
return false;
}
return true;
}
}
/**
* A key / value store column. Only available in Postgres.
*/
class HSTORE extends ABSTRACT {
validate(value) {
if (!_.isPlainObject(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid hstore', value));
}
return true;
}
}
/**
* A JSON string column. Available in MySQL, Postgres and SQLite
*/
class JSONTYPE extends ABSTRACT {
validate() {
return true;
}
_stringify(value) {
return JSON.stringify(value);
}
}
/**
* A binary storage JSON column. Only available in Postgres.
*/
class JSONB extends JSONTYPE {
}
/**
* A default value of the current timestamp
*/
class NOW extends ABSTRACT {
}
/**
* Binary storage
*/
class BLOB extends ABSTRACT {
/**
* @param {string} [length=''] could be tiny, medium, long.
*/
constructor(length) {
super();
const options = typeof length === 'object' && length || { length };
this.options = options;
this._length = options.length || '';
}
toSql() {
switch (this._length.toLowerCase()) {
case 'tiny':
return 'TINYBLOB';
case 'medium':
return 'MEDIUMBLOB';
case 'long':
return 'LONGBLOB';
default:
return this.key;
}
}
validate(value) {
if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid blob', value));
}
return true;
}
_stringify(value) {
if (!Buffer.isBuffer(value)) {
if (Array.isArray(value)) {
value = Buffer.from(value);
}
else {
value = Buffer.from(value.toString());
}
}
const hex = value.toString('hex');
return this._hexify(hex);
}
_hexify(hex) {
return `X'${hex}'`;
}
_bindParam(value, options) {
if (!Buffer.isBuffer(value)) {
if (Array.isArray(value)) {
value = Buffer.from(value);
}
else {
value = Buffer.from(value.toString());
}
}
return options.bindParam(value);
}
}
BLOB.prototype.escape = false;
/**
* Range types are data types representing a range of values of some element type (called the range's subtype).
* Only available in Postgres. See [the Postgres documentation](http://www.postgresql.org/docs/9.4/static/rangetypes.html) for more details
*/
class RANGE extends ABSTRACT {
/**
* @param {ABSTRACT} subtype A subtype for range, like RANGE(DATE)
*/
constructor(subtype) {
super();
const options = _.isPlainObject(subtype) ? subtype : { subtype };
if (!options.subtype)
options.subtype = new INTEGER();
if (typeof options.subtype === 'function') {
options.subtype = new options.subtype();
}
this._subtype = options.subtype.key;
this.options = options;
}
validate(value) {
if (!Array.isArray(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid range', value));
}
if (value.length !== 2) {
throw new sequelizeErrors.ValidationError('A range must be an array with two elements');
}
return true;
}
}
/**
* A column storing a unique universal identifier.
* Use with `UUIDV1` or `UUIDV4` for default values.
*/
class UUID extends ABSTRACT {
validate(value, options) {
if (typeof value !== 'string' || !Validator.isUUID(value) && (!options || !options.acceptStrings)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuid', value));
}
return true;
}
}
/**
* A default unique universal identifier generated following the UUID v1 standard
*/
class UUIDV1 extends ABSTRACT {
validate(value, options) {
if (typeof value !== 'string' || !Validator.isUUID(value) && (!options || !options.acceptStrings)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuid', value));
}
return true;
}
}
/**
* A default unique universal identifier generated following the UUID v4 standard
*/
class UUIDV4 extends ABSTRACT {
validate(value, options) {
if (typeof value !== 'string' || !Validator.isUUID(value, 4) && (!options || !options.acceptStrings)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuidv4', value));
}
return true;
}
}
/**
* A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model that is returned to the user but not stored in the DB.
*
* You could also use it to validate a value before permuting and storing it. VIRTUAL also takes a return type and dependency fields as arguments
* If a virtual attribute is present in `attributes` it will automatically pull in the extra fields as well.
* Return type is mostly useful for setups that rely on types like GraphQL.
*
* @example <caption>Checking password length before hashing it</caption>
* sequelize.define('user', {
* password_hash: DataTypes.STRING,
* password: {
* type: DataTypes.VIRTUAL,
* set: function (val) {
* // Remember to set the data value, otherwise it won't be validated
* this.setDataValue('password', val);
* this.setDataValue('password_hash', this.salt + val);
* },
* validate: {
* isLongEnough: function (val) {
* if (val.length < 7) {
* throw new Error("Please choose a longer password")
* }
* }
* }
* }
* })
*
* # In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB.
*
* @example <caption>Virtual with dependency fields</caption>
* {
* active: {
* type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']),
* get: function() {
* return this.get('createdAt') > Date.now() - (7 * 24 * 60 * 60 * 1000)
* }
* }
* }
*
*/
class VIRTUAL extends ABSTRACT {
/**
* @param {ABSTRACT} [ReturnType] return type for virtual type
* @param {Array} [fields] array of fields this virtual type is dependent on
*/
constructor(ReturnType, fields) {
super();
if (typeof ReturnType === 'function')
ReturnType = new ReturnType();
this.returnType = ReturnType;
this.fields = fields;
}
}
/**
* An enumeration, Postgres Only
*
* @example
* DataTypes.ENUM('value', 'another value')
* DataTypes.ENUM(['value', 'another value'])
* DataTypes.ENUM({
* values: ['value', 'another value']
* })
*/
class ENUM extends ABSTRACT {
/**
* @param {...any|{ values: any[] }|any[]} args either array of values or options object with values array. It also supports variadic values
*/
constructor(...args) {
super();
const value = args[0];
const options = typeof value === 'object' && !Array.isArray(value) && value || {
values: args.reduce((result, element) => {
return result.concat(Array.isArray(element) ? element : [element]);
}, [])
};
this.values = options.values;
this.options = options;
}
validate(value) {
if (!this.values.includes(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid choice in %j', value, this.values));
}
return true;
}
}
/**
* An array of `type`. Only available in Postgres.
*
* @example
* DataTypes.ARRAY(DataTypes.DECIMAL)
*/
class ARRAY extends ABSTRACT {
/**
* @param {ABSTRACT} type type of array values
*/
constructor(type) {
super();
const options = _.isPlainObject(type) ? type : { type };
this.options = options;
this.type = typeof options.type === 'function' ? new options.type() : options.type;
}
toSql() {
return `${this.type.toSql()}[]`;
}
validate(value) {
if (!Array.isArray(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid array', value));
}
return true;
}
static is(obj, type) {
return obj instanceof ARRAY && obj.type instanceof type;
}
}
/**
* A column storing Geometry information.
* It is only available in PostgreSQL (with PostGIS), MariaDB or MySQL.
*
* GeoJSON is accepted as input and returned as output.
*
* In PostGIS, the GeoJSON is parsed using the PostGIS function `ST_GeomFromGeoJSON`.
* In MySQL it is parsed using the function `GeomFromText`.
*
* Therefore, one can just follow the [GeoJSON spec](http://geojson.org/geojson-spec.html) for handling geometry objects. See the following examples:
*
* @example <caption>Defining a Geometry type attribute</caption>
* DataTypes.GEOMETRY
* DataTypes.GEOMETRY('POINT')
* DataTypes.GEOMETRY('POINT', 4326)
*
* @example <caption>Create a new point</caption>
* const point = { type: 'Point', coordinates: [39.807222,-76.984722]};
*
* User.create({username: 'username', geometry: point });
*
* @example <caption>Create a new linestring</caption>
* const line = { type: 'LineString', 'coordinates': [ [100.0, 0.0], [101.0, 1.0] ] };
*
* User.create({username: 'username', geometry: line });
*
* @example <caption>Create a new polygon</caption>
* const polygon = { type: 'Polygon', coordinates: [
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
* [100.0, 1.0], [100.0, 0.0] ]
* ]};
*
* User.create({username: 'username', geometry: polygon });
*
* @example <caption>Create a new point with a custom SRID</caption>
* const point = {
* type: 'Point',
* coordinates: [39.807222,-76.984722],
* crs: { type: 'name', properties: { name: 'EPSG:4326'} }
* };
*
* User.create({username: 'username', geometry: point })
*
*
* @see {@link DataTypes.GEOGRAPHY}
*/
class GEOMETRY extends ABSTRACT {
/**
* @param {string} [type] Type of geometry data
* @param {string} [srid] SRID of type
*/
constructor(type, srid) {
super();
const options = _.isPlainObject(type) ? type : { type, srid };
this.options = options;
this.type = options.type;
this.srid = options.srid;
}
_stringify(value, options) {
return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`;
}
_bindParam(value, options) {
return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`;
}
}
GEOMETRY.prototype.escape = false;
/**
* A geography datatype represents two dimensional spacial objects in an elliptic coord system.
*
* __The difference from geometry and geography type:__
*
* PostGIS 1.5 introduced a new spatial type called geography, which uses geodetic measurement instead of Cartesian measurement.
* Coordinate points in the geography type are always represented in WGS 84 lon lat degrees (SRID 4326),
* but measurement functions and relationships ST_Distance, ST_DWithin, ST_Length, and ST_Area always return answers in meters or assume inputs in meters.
*
* __What is best to use? It depends:__
*
* When choosing between the geometry and geography type for data storage, you should consider what you’ll be using it for.
* If all you do are simple measurements and relationship checks on your data, and your data covers a fairly large area, then most likely you’ll be better off storing your data using the new geography type.
* Although the new geography data type can cover the globe, the geometry type is far from obsolete.
* The geometry type has a much richer set of functions than geography, relationship checks are generally faster, and it has wider support currently across desktop and web-mapping tools
*
* @example <caption>Defining a Geography type attribute</caption>
* DataTypes.GEOGRAPHY
* DataTypes.GEOGRAPHY('POINT')
* DataTypes.GEOGRAPHY('POINT', 4326)
*/
class GEOGRAPHY extends ABSTRACT {
/**
* @param {string} [type] Type of geography data
* @param {string} [srid] SRID of type
*/
constructor(type, srid) {
super();
const options = _.isPlainObject(type) ? type : { type, srid };
this.options = options;
this.type = options.type;
this.srid = options.srid;
}
_stringify(value, options) {
return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`;
}
_bindParam(value, options) {
return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`;
}
}
GEOGRAPHY.prototype.escape = false;
/**
* The cidr type holds an IPv4 or IPv6 network specification. Takes 7 or 19 bytes.
*
* Only available for Postgres
*/
class CIDR extends ABSTRACT {
validate(value) {
if (typeof value !== 'string' || !Validator.isIPRange(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid CIDR', value));
}
return true;
}
}
/**
* The INET type holds an IPv4 or IPv6 host address, and optionally its subnet. Takes 7 or 19 bytes
*
* Only available for Postgres
*/
class INET extends ABSTRACT {
validate(value) {
if (typeof value !== 'string' || !Validator.isIP(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid INET', value));
}
return true;
}
}
/**
* The MACADDR type stores MAC addresses. Takes 6 bytes
*
* Only available for Postgres
*
*/
class MACADDR extends ABSTRACT {
validate(value) {
if (typeof value !== 'string' || !Validator.isMACAddress(value)) {
throw new sequelizeErrors.ValidationError(util.format('%j is not a valid MACADDR', value));
}
return true;
}
}
/**
* A convenience class holding commonly used data types. The data types are used when defining a new model using `Sequelize.define`, like this:
* ```js
* sequelize.define('model', {
* column: DataTypes.INTEGER
* })
* ```
* When defining a model you can just as easily pass a string as type, but often using the types defined here is beneficial. For example, using `DataTypes.BLOB`, mean
* that that column will be returned as an instance of `Buffer` when being fetched by sequelize.
*
* To provide a length for the data type, you can invoke it like a function: `INTEGER(2)`
*
* Some data types have special properties that can be accessed in order to change the data type.
* For example, to get an unsigned integer with zerofill you can do `DataTypes.INTEGER.UNSIGNED.ZEROFILL`.
* The order you access the properties in do not matter, so `DataTypes.INTEGER.ZEROFILL.UNSIGNED` is fine as well.
*
* * All number types (`INTEGER`, `BIGINT`, `FLOAT`, `DOUBLE`, `REAL`, `DECIMAL`) expose the properties `UNSIGNED` and `ZEROFILL`
* * The `CHAR` and `STRING` types expose the `BINARY` property
*
* Three of the values provided here (`NOW`, `UUIDV1` and `UUIDV4`) are special default values, that should not be used to define types. Instead they are used as shorthands for
* defining default values. For example, to get a uuid field with a default value generated following v1 of the UUID standard:
* ```js
* sequelize.define('model', {
* uuid: {
* type: DataTypes.UUID,
* defaultValue: DataTypes.UUIDV1,
* primaryKey: true
* }
* })
* ```
* There may be times when you want to generate your own UUID conforming to some other algorithm. This is accomplished
* using the defaultValue property as well, but instead of specifying one of the supplied UUID types, you return a value
* from a function.
* ```js
* sequelize.define('model', {
* uuid: {
* type: DataTypes.UUID,
* defaultValue: function() {
* return generateMyId()
* },
* primaryKey: true
* }
* })
* ```
*/
const DataTypes = module.exports = {
ABSTRACT,
STRING,
CHAR,
TEXT,
NUMBER,
TINYINT,
SMALLINT,
MEDIUMINT,
INTEGER,
BIGINT,
FLOAT,
TIME,
DATE,
DATEONLY,
BOOLEAN,
NOW,
BLOB,
DECIMAL,
NUMERIC: DECIMAL,
UUID,
UUIDV1,
UUIDV4,
HSTORE,
JSON: JSONTYPE,
JSONB,
VIRTUAL,
ARRAY,
ENUM,
RANGE,
REAL,
'DOUBLE PRECISION': DOUBLE,
DOUBLE,
GEOMETRY,
GEOGRAPHY,
CIDR,
INET,
MACADDR,
CITEXT
};
_.each(DataTypes, (dataType, name) => {
// guard for aliases
if (!Object.prototype.hasOwnProperty.call(dataType, 'key')) {
dataType.types = {};
dataType.key = dataType.prototype.key = name;
}
});
const dialectMap = {};
dialectMap.postgres = require('./dialects/postgres/data-types')(DataTypes);
dialectMap.mysql = require('./dialects/mysql/data-types')(DataTypes);
dialectMap.mariadb = require('./dialects/mariadb/data-types')(DataTypes);
dialectMap.sqlite = require('./dialects/sqlite/data-types')(DataTypes);
dialectMap.mssql = require('./dialects/mssql/data-types')(DataTypes);
const dialectList = _.values(dialectMap);
for (const dataTypes of dialectList) {
_.each(dataTypes, (DataType, key) => {
if (!DataType.key) {
DataType.key = DataType.prototype.key = key;
}
});
}
// Wrap all data types to not require `new`
for (const dataTypes of [DataTypes, ...dialectList]) {
_.each(dataTypes, (DataType, key) => {
dataTypes[key] = classToInvokable(DataType);
});
}
Object.assign(DataTypes, dialectMap);