UNPKG

sequelize-ibmi

Version:

Multi dialect ORM for Node.JS

529 lines (475 loc) 14.3 kB
'use strict'; const _ = require('lodash'); const wkx = require('wkx'); module.exports = BaseTypes => { const warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'http://www.postgresql.org/docs/9.4/static/datatype.html'); /** * Removes unsupported Postgres options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. * * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { if (dataType._length || dataType.options.length || dataType._unsigned || dataType._zerofill) { warn(`PostgresSQL does not support '${dataType.key}' with LENGTH, UNSIGNED or ZEROFILL. Plain '${dataType.key}' will be used instead.`); dataType._length = undefined; dataType.options.length = undefined; dataType._unsigned = undefined; dataType._zerofill = undefined; } } /** * types: * { * oids: [oid], * array_oids: [oid] * } * * @see oid here https://github.com/lib/pq/blob/master/oid/types.go */ BaseTypes.UUID.types.postgres = ['uuid']; BaseTypes.CIDR.types.postgres = ['cidr']; BaseTypes.INET.types.postgres = ['inet']; BaseTypes.MACADDR.types.postgres = ['macaddr']; BaseTypes.TSVECTOR.types.postgres = ['tsvector']; BaseTypes.JSON.types.postgres = ['json']; BaseTypes.JSONB.types.postgres = ['jsonb']; BaseTypes.TIME.types.postgres = ['time']; class DATEONLY extends BaseTypes.DATEONLY { _stringify(value, options) { if (value === Infinity) { return 'Infinity'; } if (value === -Infinity) { return '-Infinity'; } return super._stringify(value, options); } _sanitize(value, options) { if ((!options || options && !options.raw) && value !== Infinity && value !== -Infinity) { if (typeof value === 'string') { const lower = value.toLowerCase(); if (lower === 'infinity') { return Infinity; } if (lower === '-infinity') { return -Infinity; } } return super._sanitize(value); } return value; } static parse(value) { if (value === 'infinity') { return Infinity; } if (value === '-infinity') { return -Infinity; } return value; } } BaseTypes.DATEONLY.types.postgres = ['date']; class DECIMAL extends BaseTypes.DECIMAL { static parse(value) { return value; } } // numeric BaseTypes.DECIMAL.types.postgres = ['numeric']; class STRING extends BaseTypes.STRING { toSql() { if (this._binary) { return 'BYTEA'; } return super.toSql(); } } BaseTypes.STRING.types.postgres = ['varchar']; class TEXT extends BaseTypes.TEXT { toSql() { if (this._length) { warn('PostgreSQL does not support TEXT with options. Plain `TEXT` will be used instead.'); this._length = undefined; } return 'TEXT'; } } BaseTypes.TEXT.types.postgres = ['text']; class CITEXT extends BaseTypes.CITEXT { static parse(value) { return value; } } BaseTypes.CITEXT.types.postgres = ['citext']; class CHAR extends BaseTypes.CHAR { toSql() { if (this._binary) { return 'BYTEA'; } return super.toSql(); } } BaseTypes.CHAR.types.postgres = ['char', 'bpchar']; class BOOLEAN extends BaseTypes.BOOLEAN { toSql() { return 'BOOLEAN'; } _sanitize(value) { if (value !== null && value !== undefined) { if (Buffer.isBuffer(value) && value.length === 1) { // Bit fields are returned as buffers value = value[0]; } if (typeof value === 'string') { // Only take action on valid boolean strings. return value === 'true' || value === 't' ? true : value === 'false' || value === 'f' ? false : value; } if (typeof value === 'number') { // Only take action on valid boolean integers. return value === 1 ? true : value === 0 ? false : value; } } return value; } } BOOLEAN.parse = BOOLEAN.prototype._sanitize; BaseTypes.BOOLEAN.types.postgres = ['bool']; class DATE extends BaseTypes.DATE { toSql() { return 'TIMESTAMP WITH TIME ZONE'; } validate(value) { if (value !== Infinity && value !== -Infinity) { return super.validate(value); } return true; } _stringify(value, options) { if (value === Infinity) { return 'Infinity'; } if (value === -Infinity) { return '-Infinity'; } return super._stringify(value, options); } _sanitize(value, options) { if ((!options || options && !options.raw) && !(value instanceof Date) && !!value && value !== Infinity && value !== -Infinity) { if (typeof value === 'string') { const lower = value.toLowerCase(); if (lower === 'infinity') { return Infinity; } if (lower === '-infinity') { return -Infinity; } } return new Date(value); } return value; } } BaseTypes.DATE.types.postgres = ['timestamptz']; class TINYINT extends BaseTypes.TINYINT { constructor(length) { super(length); removeUnsupportedIntegerOptions(this); } } // int2 BaseTypes.TINYINT.types.postgres = ['int2']; class SMALLINT extends BaseTypes.SMALLINT { constructor(length) { super(length); removeUnsupportedIntegerOptions(this); } } // int2 BaseTypes.SMALLINT.types.postgres = ['int2']; class INTEGER extends BaseTypes.INTEGER { constructor(length) { super(length); removeUnsupportedIntegerOptions(this); } } INTEGER.parse = function parse(value) { return parseInt(value, 10); }; // int4 BaseTypes.INTEGER.types.postgres = ['int4']; class BIGINT extends BaseTypes.BIGINT { constructor(length) { super(length); removeUnsupportedIntegerOptions(this); } } // int8 BaseTypes.BIGINT.types.postgres = ['int8']; class REAL extends BaseTypes.REAL { constructor(length) { super(length); removeUnsupportedIntegerOptions(this); } } // float4 BaseTypes.REAL.types.postgres = ['float4']; class DOUBLE extends BaseTypes.DOUBLE { constructor(length) { super(length); removeUnsupportedIntegerOptions(this); } } // float8 BaseTypes.DOUBLE.types.postgres = ['float8']; class FLOAT extends BaseTypes.FLOAT { constructor(length, decimals) { super(length, decimals); // POSTGRES does only support lengths as parameter. // Values between 1-24 result in REAL // Values between 25-53 result in DOUBLE PRECISION // If decimals are provided remove these and print a warning if (this._decimals) { warn('PostgreSQL does not support FLOAT with decimals. Plain `FLOAT` will be used instead.'); this._length = undefined; this.options.length = undefined; this._decimals = undefined; } if (this._unsigned) { warn('PostgreSQL does not support FLOAT unsigned. `UNSIGNED` was removed.'); this._unsigned = undefined; } if (this._zerofill) { warn('PostgreSQL does not support FLOAT zerofill. `ZEROFILL` was removed.'); this._zerofill = undefined; } } } delete FLOAT.parse; // Float has no separate type in PG class BLOB extends BaseTypes.BLOB { toSql() { if (this._length) { warn('PostgreSQL does not support BLOB (BYTEA) with options. Plain `BYTEA` will be used instead.'); this._length = undefined; } return 'BYTEA'; } _hexify(hex) { // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html return `E'\\\\x${hex}'`; } } BaseTypes.BLOB.types.postgres = ['bytea']; class GEOMETRY extends BaseTypes.GEOMETRY { toSql() { let result = this.key; if (this.type) { result += `(${this.type}`; if (this.srid) { result += `,${this.srid}`; } result += ')'; } return result; } static parse(value) { const b = Buffer.from(value, 'hex'); return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); } _stringify(value, options) { return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; } _bindParam(value, options) { return `ST_GeomFromGeoJSON(${options.bindParam(value)})`; } } BaseTypes.GEOMETRY.types.postgres = ['geometry']; class GEOGRAPHY extends BaseTypes.GEOGRAPHY { toSql() { let result = 'GEOGRAPHY'; if (this.type) { result += `(${this.type}`; if (this.srid) { result += `,${this.srid}`; } result += ')'; } return result; } static parse(value) { const b = Buffer.from(value, 'hex'); return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); } _stringify(value, options) { return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; } bindParam(value, options) { return `ST_GeomFromGeoJSON(${options.bindParam(value)})`; } } BaseTypes.GEOGRAPHY.types.postgres = ['geography']; let hstore; class HSTORE extends BaseTypes.HSTORE { constructor() { super(); if (!hstore) { // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated hstore = require('./hstore'); } } _value(value) { if (!hstore) { // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated hstore = require('./hstore'); } return hstore.stringify(value); } _stringify(value) { return `'${this._value(value)}'`; } _bindParam(value, options) { return options.bindParam(this._value(value)); } static parse(value) { if (!hstore) { // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated hstore = require('./hstore'); } return hstore.parse(value); } } HSTORE.prototype.escape = false; BaseTypes.HSTORE.types.postgres = ['hstore']; class RANGE extends BaseTypes.RANGE { _value(values, options) { if (!Array.isArray(values)) { return this.options.subtype.stringify(values, options); } const valueInclusivity = [true, false]; const valuesStringified = values.map((value, index) => { if (_.isObject(value) && Object.prototype.hasOwnProperty.call(value, 'value')) { if (Object.prototype.hasOwnProperty.call(value, 'inclusive')) { valueInclusivity[index] = value.inclusive; } value = value.value; } if (value === null || value === -Infinity || value === Infinity) { // Pass through "unbounded" bounds unchanged return value; } if (this.options.subtype.stringify) { return this.options.subtype.stringify(value, options); } return options.escape(value); }); // Array.map does not preserve extra array properties valuesStringified.inclusive = valueInclusivity; return range.stringify(valuesStringified); } _stringify(values, options) { const value = this._value(values, options); if (!Array.isArray(values)) { return `'${value}'::${this.toCastType()}`; } return `'${value}'`; } _bindParam(values, options) { const value = this._value(values, options); if (!Array.isArray(values)) { return `${options.bindParam(value)}::${this.toCastType()}`; } return options.bindParam(value); } toSql() { return BaseTypes.RANGE.types.postgres.subtypes[this._subtype.toLowerCase()]; } toCastType() { return BaseTypes.RANGE.types.postgres.castTypes[this._subtype.toLowerCase()]; } static parse(value, options = { parser: val => val }) { return range.parse(value, options.parser); } } const range = require('./range'); RANGE.prototype.escape = false; BaseTypes.RANGE.types.postgres = { subtypes: { integer: 'int4range', decimal: 'numrange', date: 'tstzrange', dateonly: 'daterange', bigint: 'int8range' }, castTypes: { integer: 'int4', decimal: 'numeric', date: 'timestamptz', dateonly: 'date', bigint: 'int8' } }; // TODO: Why are base types being manipulated?? BaseTypes.ARRAY.prototype.escape = false; BaseTypes.ARRAY.prototype._value = function _value(values, options) { return values.map(value => { if (options && options.bindParam && this.type && this.type._value) { return this.type._value(value, options); } if (this.type && this.type.stringify) { value = this.type.stringify(value, options); if (this.type.escape === false) { return value; } } return options.escape(value); }, this); }; BaseTypes.ARRAY.prototype._stringify = function _stringify(values, options) { let str = `ARRAY[${this._value(values, options).join(',')}]`; if (this.type) { const Utils = require('../../utils'); let castKey = this.toSql(); if (this.type instanceof BaseTypes.ENUM) { castKey = `${Utils.addTicks( Utils.generateEnumName(options.field.Model.getTableName(), options.field.field), '"' ) }[]`; } str += `::${castKey}`; } return str; }; BaseTypes.ARRAY.prototype._bindParam = function _bindParam(values, options) { return options.bindParam(this._value(values, options)); }; class ENUM extends BaseTypes.ENUM { static parse(value) { return value; } } BaseTypes.ENUM.types.postgres = [null]; return { DECIMAL, BLOB, STRING, CHAR, TEXT, CITEXT, TINYINT, SMALLINT, INTEGER, BIGINT, BOOLEAN, DATE, DATEONLY, REAL, 'DOUBLE PRECISION': DOUBLE, FLOAT, GEOMETRY, GEOGRAPHY, HSTORE, RANGE, ENUM }; };