不要怂,就是干,撸起袖子干!

Commit 4f74575a by Jan Aagaard Meier

feat(datatypes) Initial work on datatypes refactor

1 parent 1b8d2ade
'use strict'; 'use strict';
/*jshint -W110 */
var util = require('util') var util = require('util')
, _ = require('lodash') , _ = require('lodash')
, Wkt = require('wellknown') , Wkt = require('wellknown')
, sequelizeErrors = require('./errors') , sequelizeErrors = require('./errors')
, warnings = {} , Validator = require('validator')
, Validator = require('validator'); , moment = require('moment-timezone')
, hstore = require('./dialects/postgres/hstore')
, wkx = require('wkx')
, deprecate = require('depd')('DataTypes');
/** /**
* A convenience class holding commonly used data types. The datatypes are used when defining a new model using `Sequelize.define`, like this: * A convenience class holding commonly used data types. The datatypes are used when defining a new model using `Sequelize.define`, like this:
...@@ -39,7 +43,6 @@ var util = require('util') ...@@ -39,7 +43,6 @@ var util = require('util')
*/ */
var ABSTRACT = function(options) { var ABSTRACT = function(options) {
}; };
ABSTRACT.prototype.dialectTypes = ''; ABSTRACT.prototype.dialectTypes = '';
...@@ -51,12 +54,36 @@ ABSTRACT.prototype.toSql = function() { ...@@ -51,12 +54,36 @@ ABSTRACT.prototype.toSql = function() {
return this.key; return this.key;
}; };
ABSTRACT.prototype.warn = function(text) { ABSTRACT.prototype.warn = function(text) {
if (!warnings[text]) { deprecate(text + '\n>> Check: ' + this.dialectTypes);
warnings[text] = true; };
console.warn('>> WARNING:', text, '\n>> Check:', this.dialectTypes); ABSTRACT.prototype.stringify = function (value, options) {
if (this.$stringify) {
return this.$stringify(value, options);
} }
return value;
}; };
ABSTRACT.inherits = function (Constructor) {
var baseType = this;
if (!Constructor) {
Constructor = function () {
if (!(this instanceof Constructor)) {
var args = [null].concat(arguments);
var FactoryFunction = Constructor.bind.apply(Constructor, args);
return new FactoryFunction();
}
baseType.apply(this, arguments);
};
}
util.inherits(Constructor, baseType); // Instance (prototype) methods
_.extend(Constructor, this); // Static methods
return Constructor;
};
/** /**
* A variable length string. Default length 255 * A variable length string. Default length 255
* *
...@@ -64,7 +91,7 @@ ABSTRACT.prototype.warn = function(text) { ...@@ -64,7 +91,7 @@ ABSTRACT.prototype.warn = function(text) {
* *
* @property STRING * @property STRING
*/ */
var STRING = function(length, binary) { var STRING = ABSTRACT.inherits(function(length, binary) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
binary: binary binary: binary
...@@ -75,8 +102,7 @@ var STRING = function(length, binary) { ...@@ -75,8 +102,7 @@ var STRING = function(length, binary) {
this.options = options; this.options = options;
this._binary = options.binary; this._binary = options.binary;
this._length = options.length || 255; this._length = options.length || 255;
}; });
util.inherits(STRING, ABSTRACT);
STRING.prototype.key = STRING.key = 'STRING'; STRING.prototype.key = STRING.key = 'STRING';
STRING.prototype.toSql = function() { STRING.prototype.toSql = function() {
...@@ -107,7 +133,7 @@ Object.defineProperty(STRING.prototype, 'BINARY', { ...@@ -107,7 +133,7 @@ Object.defineProperty(STRING.prototype, 'BINARY', {
* *
* @property CHAR * @property CHAR
*/ */
var CHAR = function(length, binary) { var CHAR = STRING.inherits(function(length, binary) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
binary: binary binary: binary
...@@ -115,8 +141,7 @@ var CHAR = function(length, binary) { ...@@ -115,8 +141,7 @@ var CHAR = function(length, binary) {
if (!(this instanceof CHAR)) return new CHAR(options); if (!(this instanceof CHAR)) return new CHAR(options);
STRING.apply(this, arguments); STRING.apply(this, arguments);
}; });
util.inherits(CHAR, STRING);
CHAR.prototype.key = CHAR.key = 'CHAR'; CHAR.prototype.key = CHAR.key = 'CHAR';
CHAR.prototype.toSql = function() { CHAR.prototype.toSql = function() {
...@@ -127,14 +152,13 @@ CHAR.prototype.toSql = function() { ...@@ -127,14 +152,13 @@ CHAR.prototype.toSql = function() {
* An (un)limited length text column. Available lengths: `tiny`, `medium`, `long` * An (un)limited length text column. Available lengths: `tiny`, `medium`, `long`
* @property TEXT * @property TEXT
*/ */
var TEXT = function(length) { var TEXT = ABSTRACT.inherits(function(length) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length length: length
}; };
if (!(this instanceof TEXT)) return new TEXT(options); if (!(this instanceof TEXT)) return new TEXT(options);
this._length = options.length || ''; this._length = options.length || '';
}; });
util.inherits(TEXT, ABSTRACT);
TEXT.prototype.key = TEXT.key = 'TEXT'; TEXT.prototype.key = TEXT.key = 'TEXT';
TEXT.prototype.toSql = function() { TEXT.prototype.toSql = function() {
...@@ -157,7 +181,7 @@ TEXT.prototype.validate = function(value) { ...@@ -157,7 +181,7 @@ TEXT.prototype.validate = function(value) {
return true; return true;
}; };
var NUMBER = function(options) { var NUMBER = ABSTRACT.inherits(function(options) {
this.options = options; this.options = options;
this._length = options.length; this._length = options.length;
this._zerofill = options.zerofill; this._zerofill = options.zerofill;
...@@ -165,8 +189,7 @@ var NUMBER = function(options) { ...@@ -165,8 +189,7 @@ var NUMBER = function(options) {
this._precision = options.precision; this._precision = options.precision;
this._scale = options.scale; this._scale = options.scale;
this._unsigned = options.unsigned; this._unsigned = options.unsigned;
}; });
util.inherits(NUMBER, ABSTRACT);
NUMBER.prototype.key = NUMBER.key = 'NUMBER'; NUMBER.prototype.key = NUMBER.key = 'NUMBER';
NUMBER.prototype.toSql = function() { NUMBER.prototype.toSql = function() {
...@@ -216,14 +239,13 @@ Object.defineProperty(NUMBER.prototype, 'ZEROFILL', { ...@@ -216,14 +239,13 @@ Object.defineProperty(NUMBER.prototype, 'ZEROFILL', {
* *
* @property INTEGER * @property INTEGER
*/ */
var INTEGER = function(length) { var INTEGER = NUMBER.inherits(function(length) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length length: length
}; };
if (!(this instanceof INTEGER)) return new INTEGER(options); if (!(this instanceof INTEGER)) return new INTEGER(options);
NUMBER.call(this, options); NUMBER.call(this, options);
}; });
util.inherits(INTEGER, NUMBER);
INTEGER.prototype.key = INTEGER.key = 'INTEGER'; INTEGER.prototype.key = INTEGER.key = 'INTEGER';
INTEGER.prototype.validate = function(value) { INTEGER.prototype.validate = function(value) {
...@@ -242,14 +264,13 @@ INTEGER.prototype.validate = function(value) { ...@@ -242,14 +264,13 @@ INTEGER.prototype.validate = function(value) {
* @property BIGINT * @property BIGINT
*/ */
var BIGINT = function(length) { var BIGINT = NUMBER.inherits(function(length) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length length: length
}; };
if (!(this instanceof BIGINT)) return new BIGINT(options); if (!(this instanceof BIGINT)) return new BIGINT(options);
NUMBER.call(this, options); NUMBER.call(this, options);
}; });
util.inherits(BIGINT, NUMBER);
BIGINT.prototype.key = BIGINT.key = 'BIGINT'; BIGINT.prototype.key = BIGINT.key = 'BIGINT';
BIGINT.prototype.validate = function(value) { BIGINT.prototype.validate = function(value) {
...@@ -267,15 +288,14 @@ BIGINT.prototype.validate = function(value) { ...@@ -267,15 +288,14 @@ BIGINT.prototype.validate = function(value) {
* *
* @property FLOAT * @property FLOAT
*/ */
var FLOAT = function(length, decimals) { var FLOAT = NUMBER.inherits(function(length, decimals) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
decimals: decimals decimals: decimals
}; };
if (!(this instanceof FLOAT)) return new FLOAT(options); if (!(this instanceof FLOAT)) return new FLOAT(options);
NUMBER.call(this, options); NUMBER.call(this, options);
}; });
util.inherits(FLOAT, NUMBER);
FLOAT.prototype.key = FLOAT.key = 'FLOAT'; FLOAT.prototype.key = FLOAT.key = 'FLOAT';
FLOAT.prototype.validate = function(value) { FLOAT.prototype.validate = function(value) {
...@@ -293,15 +313,14 @@ FLOAT.prototype.validate = function(value) { ...@@ -293,15 +313,14 @@ FLOAT.prototype.validate = function(value) {
* *
* @property REAL * @property REAL
*/ */
var REAL = function(length, decimals) { var REAL = NUMBER.inherits(function(length, decimals) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
decimals: decimals decimals: decimals
}; };
if (!(this instanceof REAL)) return new REAL(options); if (!(this instanceof REAL)) return new REAL(options);
NUMBER.call(this, options); NUMBER.call(this, options);
}; });
util.inherits(REAL, NUMBER);
REAL.prototype.key = REAL.key = 'REAL'; REAL.prototype.key = REAL.key = 'REAL';
...@@ -312,15 +331,14 @@ REAL.prototype.key = REAL.key = 'REAL'; ...@@ -312,15 +331,14 @@ REAL.prototype.key = REAL.key = 'REAL';
* *
* @property DOUBLE * @property DOUBLE
*/ */
var DOUBLE = function(length, decimals) { var DOUBLE = NUMBER.inherits(function(length, decimals) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
decimals: decimals decimals: decimals
}; };
if (!(this instanceof DOUBLE)) return new DOUBLE(options); if (!(this instanceof DOUBLE)) return new DOUBLE(options);
NUMBER.call(this, options); NUMBER.call(this, options);
}; });
util.inherits(DOUBLE, NUMBER);
DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION'; DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION';
...@@ -331,15 +349,14 @@ DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION'; ...@@ -331,15 +349,14 @@ DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION';
* *
* @property DECIMAL * @property DECIMAL
*/ */
var DECIMAL = function(precision, scale) { var DECIMAL = NUMBER.inherits(function(precision, scale) {
var options = typeof precision === 'object' && precision || { var options = typeof precision === 'object' && precision || {
precision: precision, precision: precision,
scale: scale scale: scale
}; };
if (!(this instanceof DECIMAL)) return new DECIMAL(options); if (!(this instanceof DECIMAL)) return new DECIMAL(options);
NUMBER.call(this, options); NUMBER.call(this, options);
}; });
util.inherits(DECIMAL, NUMBER);
DECIMAL.prototype.key = DECIMAL.key = 'DECIMAL'; DECIMAL.prototype.key = DECIMAL.key = 'DECIMAL';
DECIMAL.prototype.toSql = function() { DECIMAL.prototype.toSql = function() {
...@@ -361,11 +378,7 @@ DECIMAL.prototype.validate = function(value) { ...@@ -361,11 +378,7 @@ DECIMAL.prototype.validate = function(value) {
* A boolean / tinyint column, depending on dialect * A boolean / tinyint column, depending on dialect
* @property BOOLEAN * @property BOOLEAN
*/ */
var BOOLEAN = function() { var BOOLEAN = ABSTRACT.inherits();
if (!(this instanceof BOOLEAN)) return new BOOLEAN();
ABSTRACT.apply(this, arguments);
};
util.inherits(BOOLEAN, ABSTRACT);
BOOLEAN.prototype.key = BOOLEAN.key = 'BOOLEAN'; BOOLEAN.prototype.key = BOOLEAN.key = 'BOOLEAN';
BOOLEAN.prototype.toSql = function() { BOOLEAN.prototype.toSql = function() {
...@@ -384,11 +397,7 @@ BOOLEAN.prototype.validate = function(value) { ...@@ -384,11 +397,7 @@ BOOLEAN.prototype.validate = function(value) {
* @property TIME * @property TIME
*/ */
var TIME = function() { var TIME = ABSTRACT.inherits();
if (!(this instanceof TIME)) return new TIME();
ABSTRACT.apply(this, arguments);
};
util.inherits(TIME, ABSTRACT);
TIME.prototype.key = TIME.key = 'TIME'; TIME.prototype.key = TIME.key = 'TIME';
TIME.prototype.toSql = function() { TIME.prototype.toSql = function() {
...@@ -399,11 +408,7 @@ TIME.prototype.toSql = function() { ...@@ -399,11 +408,7 @@ TIME.prototype.toSql = function() {
* A datetime column * A datetime column
* @property DATE * @property DATE
*/ */
var DATE = function() { var DATE = ABSTRACT.inherits();
if (!(this instanceof DATE)) return new DATE();
ABSTRACT.apply(this, arguments);
};
util.inherits(DATE, ABSTRACT);
DATE.prototype.key = DATE.key = 'DATE'; DATE.prototype.key = DATE.key = 'DATE';
DATE.prototype.toSql = function() { DATE.prototype.toSql = function() {
...@@ -416,6 +421,24 @@ DATE.prototype.validate = function(value) { ...@@ -416,6 +421,24 @@ DATE.prototype.validate = function(value) {
return true; return true;
}; };
DATE.prototype.$applyTimezone = function (date, options) {
if (moment.tz.zone(options.timezone)) {
date = moment(date).tz(options.timezone);
} else {
date = moment(date).utcOffset(options.timezone);
}
return date;
};
DATE.prototype.$stringify = function (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 * A date only column
* @property DATEONLY * @property DATEONLY
...@@ -452,6 +475,11 @@ HSTORE.prototype.validate = function(value) { ...@@ -452,6 +475,11 @@ HSTORE.prototype.validate = function(value) {
return true; return true;
}; };
HSTORE.prototype.escape = false;
HSTORE.prototype.stringify = function (value) {
return '\'' + hstore.stringify(value) + '\'';
};
/** /**
* A JSON string column. Only available in postgres. * A JSON string column. Only available in postgres.
* @property JSON * @property JSON
...@@ -467,6 +495,10 @@ JSONTYPE.prototype.validate = function(value) { ...@@ -467,6 +495,10 @@ JSONTYPE.prototype.validate = function(value) {
return true; return true;
}; };
JSONTYPE.prototype.stringify = function (value, options) {
return JSON.stringify(value);
};
/** /**
* A pre-processed JSON data column. Only available in postgres. * A pre-processed JSON data column. Only available in postgres.
* @property JSONB * @property JSONB
...@@ -497,14 +529,13 @@ NOW.prototype.key = NOW.key = 'NOW'; ...@@ -497,14 +529,13 @@ NOW.prototype.key = NOW.key = 'NOW';
* @property BLOB * @property BLOB
*/ */
var BLOB = function(length) { var BLOB = ABSTRACT.inherits(function(length) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length length: length
}; };
if (!(this instanceof BLOB)) return new BLOB(options); if (!(this instanceof BLOB)) return new BLOB(options);
this._length = options.length || ''; this._length = options.length || '';
}; });
util.inherits(BLOB, ABSTRACT);
BLOB.prototype.key = BLOB.key = 'BLOB'; BLOB.prototype.key = BLOB.key = 'BLOB';
BLOB.prototype.toSql = function() { BLOB.prototype.toSql = function() {
...@@ -527,6 +558,12 @@ BLOB.prototype.validate = function(value) { ...@@ -527,6 +558,12 @@ BLOB.prototype.validate = function(value) {
return true; return true;
}; };
BLOB.prototype.$stringify = function (value) {
var hex = value.toString('hex');
return "X'" + hex + "'";
};
/** /**
* Range types are data types representing a range of values of some element type (called the range's subtype). * Range types are data types representing a range of values of some element type (called the range's subtype).
* Only available in postgres. * Only available in postgres.
...@@ -537,12 +574,17 @@ BLOB.prototype.validate = function(value) { ...@@ -537,12 +574,17 @@ BLOB.prototype.validate = function(value) {
var RANGE = function (subtype) { var RANGE = function (subtype) {
var options = _.isPlainObject(subtype) ? subtype : { subtype: subtype }; var options = _.isPlainObject(subtype) ? subtype : { subtype: subtype };
if (!options.subtype) options.subtype = INTEGER; if (!options.subtype) options.subtype = new INTEGER();
if (_.isFunction(options.subtype)) {
options.subtype = new options.subtype();
}
if (!(this instanceof RANGE)) return new RANGE(options); if (!(this instanceof RANGE)) return new RANGE(options);
ABSTRACT.apply(this, arguments); ABSTRACT.apply(this, arguments);
this._subtype = options.subtype.key; this._subtype = options.subtype.key;
this.options = options;
}; };
util.inherits(RANGE, ABSTRACT); util.inherits(RANGE, ABSTRACT);
...@@ -574,15 +616,12 @@ RANGE.prototype.validate = function(value) { ...@@ -574,15 +616,12 @@ RANGE.prototype.validate = function(value) {
return true; return true;
}; };
/** /**
* A column storing a unique univeral identifier. Use with `UUIDV1` or `UUIDV4` for default values. * A column storing a unique univeral identifier. Use with `UUIDV1` or `UUIDV4` for default values.
* @property UUID * @property UUID
*/ */
var UUID = function() { var UUID = ABSTRACT.inherits();
if (!(this instanceof UUID)) return new UUID();
ABSTRACT.apply(this, arguments);
};
util.inherits(UUID, ABSTRACT);
UUID.prototype.key = UUID.key = 'UUID'; UUID.prototype.key = UUID.key = 'UUID';
UUID.prototype.validate = function(value) { UUID.prototype.validate = function(value) {
...@@ -747,7 +786,7 @@ var helpers = { ...@@ -747,7 +786,7 @@ var helpers = {
SCALE: [DECIMAL] SCALE: [DECIMAL]
}; };
var GEOMETRY = function(type, srid) { var GEOMETRY = ABSTRACT.inherits(function(type, srid) {
var options = _.isPlainObject(type) ? type : { var options = _.isPlainObject(type) ? type : {
type: type, type: type,
srid: srid srid: srid
...@@ -758,13 +797,20 @@ var GEOMETRY = function(type, srid) { ...@@ -758,13 +797,20 @@ var GEOMETRY = function(type, srid) {
this.options = options; this.options = options;
this.type = options.type; this.type = options.type;
this.srid = options.srid; this.srid = options.srid;
}; });
util.inherits(GEOMETRY, ABSTRACT);
GEOMETRY.prototype.key = GEOMETRY.key = 'GEOMETRY'; GEOMETRY.prototype.key = GEOMETRY.key = 'GEOMETRY';
GEOMETRY.prototype.parse = function(value) { GEOMETRY.parse = GEOMETRY.prototype.parse = function(value) {
return Wkt.parse(value); // For some reason, discard the first 4 bytes
value = value.buffer().slice(4);
return wkx.Geometry.parse(value).toGeoJSON();
};
GEOMETRY.prototype.escape = false;
GEOMETRY.prototype.stringify = function (value) {
return 'GeomFromText(\'' + Wkt.stringify(value) + '\')';
}; };
Object.keys(helpers).forEach(function (helper) { Object.keys(helpers).forEach(function (helper) {
...@@ -783,7 +829,7 @@ Object.keys(helpers).forEach(function (helper) { ...@@ -783,7 +829,7 @@ Object.keys(helpers).forEach(function (helper) {
}); });
}); });
module.exports = { var dataTypes = {
ABSTRACT: ABSTRACT, ABSTRACT: ABSTRACT,
STRING: STRING, STRING: STRING,
CHAR: CHAR, CHAR: CHAR,
...@@ -816,3 +862,14 @@ module.exports = { ...@@ -816,3 +862,14 @@ module.exports = {
'DOUBLE PRECISION': DOUBLE, 'DOUBLE PRECISION': DOUBLE,
GEOMETRY: GEOMETRY GEOMETRY: GEOMETRY
}; };
_.each(dataTypes, function (dataType) {
dataType.types = {};
});
dataTypes.postgres = require('./dialects/postgres/data-types')(dataTypes);
dataTypes.mysql = require('./dialects/mysql/data-types')(dataTypes);
dataTypes.sqlite = require('./dialects/sqlite/data-types')(dataTypes);
dataTypes.mssql = require('./dialects/mssql/data-types')(dataTypes);
module.exports = dataTypes;
...@@ -943,16 +943,34 @@ var QueryGenerator = { ...@@ -943,16 +943,34 @@ var QueryGenerator = {
*/ */
escape: function(value, field, options) { escape: function(value, field, options) {
options = options || {}; options = options || {};
if (value && value._isSequelizeMethod) {
return this.handleSequelizeMethod(value); if (value) {
} else { if (value._isSequelizeMethod) {
if (['INSERT', 'UPDATE'].indexOf(options.context) !== -1 && this.typeValidation && field && field.type && value) { return this.handleSequelizeMethod(value);
if (field.type.validate) { } else {
field.type.validate(value); if (field && field.type) {
if (['INSERT', 'UPDATE'].indexOf(options.context) !== -1 && this.typeValidation && field && field.type && value) {
if (field.type.validate) {
field.type.validate(value);
}
}
if (field.type.stringify) {
// Users shouldn't have to worry about these args - just give them a function that takes a single arg
var simpleEscape = _.partialRight(SqlString.escape, this.options.timezone, this.dialect, field);
value = field.type.stringify(value, { escape: simpleEscape, field: field, timezone: this.options.timezone });
if (field.type.escape === false) {
// The data-type already did the required escaping
return value;
}
}
} }
} }
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
} }
return SqlString.escape(value, this.options.timezone, this.dialect, field);
}, },
/** /**
......
'use strict'; 'use strict';
var BaseTypes = require('../../data-types') var _ = require('lodash');
, util = require('util')
, _ = require('lodash');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx'; module.exports = function (BaseTypes) {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx';
var STRING = function() { var STRING = BaseTypes.STRING.inherits;
if (!(this instanceof STRING)) return new STRING();
BaseTypes.STRING.apply(this, arguments);
};
util.inherits(STRING, BaseTypes.STRING);
STRING.prototype.toSql = function() {
if (!this._binary) {
return 'NVARCHAR(' + this._length + ')';
} else{
return 'BINARY(' + this._length + ')';
}
};
BaseTypes.TEXT.prototype.toSql = function() { STRING.prototype.toSql = function() {
// TEXT is deprecated in mssql and it would normally be saved as a non-unicode string. if (!this._binary) {
// Using unicode is just future proof return 'NVARCHAR(' + this._length + ')';
if (this._length) { } else{
if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8 return 'BINARY(' + this._length + ')';
this.warn('MSSQL does not support TEXT with the `length` = `tiny` option. `NVARCHAR(256)` will be used instead.');
return 'NVARCHAR(256)';
} }
this.warn('MSSQL does not support TEXT with the `length` option. `NVARCHAR(MAX)` will be used instead.'); };
}
return 'NVARCHAR(MAX)'; var TEXT = BaseTypes.TEXT.inherits();
};
TEXT.prototype.toSql = function() {
// TEXT is deprecated in mssql and it would normally be saved as a non-unicode string.
// Using unicode is just future proof
if (this._length) {
if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8
this.warn('MSSQL does not support TEXT with the `length` = `tiny` option. `NVARCHAR(256)` will be used instead.');
return 'NVARCHAR(256)';
}
this.warn('MSSQL does not support TEXT with the `length` option. `NVARCHAR(MAX)` will be used instead.');
}
return 'NVARCHAR(MAX)';
};
var BOOLEAN = function() { var BOOLEAN = BaseTypes.BOOLEAN.inherits();
if (!(this instanceof BOOLEAN)) return new BOOLEAN();
BaseTypes.BOOLEAN.apply(this, arguments);
};
util.inherits(BOOLEAN, BaseTypes.BOOLEAN);
BOOLEAN.prototype.toSql = function() { BOOLEAN.prototype.toSql = function() {
return 'BIT'; return 'BIT';
}; };
BaseTypes.BLOB.prototype.toSql = function() { var BLOB = BaseTypes.BLOB.inherits();
if (this._length) {
if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8 BLOB.prototype.toSql = function() {
this.warn('MSSQL does not support BLOB with the `length` = `tiny` option. `VARBINARY(256)` will be used instead.'); if (this._length) {
return 'VARBINARY(256)'; if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8
this.warn('MSSQL does not support BLOB with the `length` = `tiny` option. `VARBINARY(256)` will be used instead.');
return 'VARBINARY(256)';
}
this.warn('MSSQL does not support BLOB with the `length` option. `VARBINARY(MAX)` will be used instead.');
} }
this.warn('MSSQL does not support BLOB with the `length` option. `VARBINARY(MAX)` will be used instead.'); return 'VARBINARY(MAX)';
} };
return 'VARBINARY(MAX)';
};
var UUID = function() { BLOB.prototype.$stringify = function (value) {
if (!(this instanceof UUID)) return new UUID(); var hex = value.toString('hex');
BaseTypes.UUID.apply(this, arguments);
};
util.inherits(UUID, BaseTypes.UUID);
UUID.prototype.toSql = function() { return '0x' + hex;
return 'CHAR(36)'; };
};
var NOW = function() { var UUID = BaseTypes.UUID.inherits();
if (!(this instanceof NOW)) return new NOW();
BaseTypes.NOW.apply(this, arguments);
};
util.inherits(NOW, BaseTypes.NOW);
NOW.prototype.toSql = function() { UUID.prototype.toSql = function() {
return 'GETDATE()'; return 'CHAR(36)';
}; };
var DATE = function() { var NOW = BaseTypes.NOW.inherits();
if (!(this instanceof DATE)) return new DATE();
BaseTypes.DATE.apply(this, arguments);
};
util.inherits(DATE, BaseTypes.DATE);
DATE.prototype.toSql = function() { NOW.prototype.toSql = function() {
return 'DATETIME2'; return 'GETDATE()';
}; };
var INTEGER = function() { var DATE = BaseTypes.DATE.inherits();
if (!(this instanceof INTEGER)) return new INTEGER();
BaseTypes.INTEGER.apply(this, arguments);
// MSSQL does not support any options for integer
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('MSSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(INTEGER, BaseTypes.INTEGER);
var BIGINT = function() {
if (!(this instanceof BIGINT)) return new BIGINT();
BaseTypes.BIGINT.apply(this, arguments);
// MSSQL does not support any options for bigint
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('MSSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(BIGINT, BaseTypes.BIGINT);
var REAL = function() {
if (!(this instanceof REAL)) return new REAL();
BaseTypes.REAL.apply(this, arguments);
// MSSQL does not support any options for real
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('MSSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(REAL, BaseTypes.REAL);
var FLOAT = function() {
if (!(this instanceof FLOAT)) return new FLOAT();
BaseTypes.FLOAT.apply(this, arguments);
// MSSQL does only support lengths as option.
// Values between 1-24 result in 7 digits precision (4 bytes storage size)
// Values between 25-53 result in 15 digits precision (8 bytes storage size)
// If decimals are provided remove these and print a warning
if (this._decimals) {
this.warn('MSSQL does not support Float with decimals. Plain `FLOAT` will be used instead.');
this._length = undefined;
this.options.length = undefined;
}
if (this._unsigned) {
this.warn('MSSQL does not support Float unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined;
}
if (this._zerofill) {
this.warn('MSSQL does not support Float zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined;
}
};
util.inherits(FLOAT, BaseTypes.FLOAT);
BaseTypes.ENUM.prototype.toSql = function() { DATE.prototype.toSql = function() {
return 'VARCHAR(255)'; return 'DATETIME2';
}; };
module.exports = { var INTEGER = BaseTypes.INTEGER.inherits(function() {
BOOLEAN: BOOLEAN, if (!(this instanceof INTEGER)) return new INTEGER();
STRING: STRING, BaseTypes.INTEGER.apply(this, arguments);
UUID: UUID,
DATE: DATE, // MSSQL does not support any options for integer
NOW: NOW, if (this._length || this.options.length || this._unsigned || this._zerofill) {
INTEGER: INTEGER, this.warn('MSSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.');
BIGINT: BIGINT, this._length = undefined;
REAL: REAL, this.options.length = undefined;
FLOAT: FLOAT this._unsigned = undefined;
}; this._zerofill = undefined;
}
});
var BIGINT = BaseTypes.BIGINT.inherits(function() {
if (!(this instanceof BIGINT)) return new BIGINT();
BaseTypes.BIGINT.apply(this, arguments);
// MSSQL does not support any options for bigint
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('MSSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
});
var REAL = BaseTypes.REAL.inherits(function() {
if (!(this instanceof REAL)) return new REAL();
BaseTypes.REAL.apply(this, arguments);
// MSSQL does not support any options for real
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('MSSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
});
var FLOAT = BaseTypes.FLOAT.inherits(function() {
if (!(this instanceof FLOAT)) return new FLOAT();
BaseTypes.FLOAT.apply(this, arguments);
// MSSQL does only support lengths as option.
// Values between 1-24 result in 7 digits precision (4 bytes storage size)
// Values between 25-53 result in 15 digits precision (8 bytes storage size)
// If decimals are provided remove these and print a warning
if (this._decimals) {
this.warn('MSSQL does not support Float with decimals. Plain `FLOAT` will be used instead.');
this._length = undefined;
this.options.length = undefined;
}
if (this._unsigned) {
this.warn('MSSQL does not support Float unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined;
}
if (this._zerofill) {
this.warn('MSSQL does not support Float zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined;
}
});
var ENUM = BaseTypes.ENUM.inherits();
ENUM.prototype.toSql = function() {
return 'VARCHAR(255)';
};
var exports = {
BLOB: BLOB,
BOOLEAN: BOOLEAN,
ENUM: ENUM,
STRING: STRING,
UUID: UUID,
DATE: DATE,
NOW: NOW,
INTEGER: INTEGER,
BIGINT: BIGINT,
REAL: REAL,
FLOAT: FLOAT,
TEXT: TEXT
};
_.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key;
if (!DataType.extend) {
DataType.extend = function(oldType) {
return new DataType(oldType.options);
};
}
});
_.forIn(module.exports, function (DataType, key) { return exports;
if (!DataType.key) DataType.key = key; };
if (!DataType.extend) {
DataType.extend = function(oldType) {
return new DataType(oldType.options);
};
}
});
...@@ -5,7 +5,7 @@ var _ = require('lodash') ...@@ -5,7 +5,7 @@ var _ = require('lodash')
, ConnectionManager = require('./connection-manager') , ConnectionManager = require('./connection-manager')
, Query = require('./query') , Query = require('./query')
, QueryGenerator = require('./query-generator') , QueryGenerator = require('./query-generator')
, DataTypes = require('./data-types'); , DataTypes = require('../../data-types').mssql;
var MssqlDialect = function(sequelize) { var MssqlDialect = function(sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
......
...@@ -4,7 +4,11 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -4,7 +4,11 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise') , Promise = require('../../promise')
, sequelizeErrors = require('../../errors'); , sequelizeErrors = require('../../errors')
, dataTypes = require('../../data-types').mysql
, _ = require('lodash')
, parserMap = {};
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -16,10 +20,23 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -16,10 +20,23 @@ ConnectionManager = function(dialect, sequelize) {
} catch (err) { } catch (err) {
throw new Error('Please install mysql package manually'); throw new Error('Please install mysql package manually');
} }
this.refreshTypeParser(dataTypes);
}; };
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype); Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = function (dataTypes) {
_.each(dataTypes, function (dataType, key) {
if (dataType.parse && dataType.types.mysql) {
dataType.types.mysql.forEach(function (type) {
parserMap[type] = dataType.parse;
});
}
});
};
ConnectionManager.prototype.connect = function(config) { ConnectionManager.prototype.connect = function(config) {
var self = this; var self = this;
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
...@@ -29,7 +46,14 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -29,7 +46,14 @@ ConnectionManager.prototype.connect = function(config) {
user: config.username, user: config.username,
password: config.password, password: config.password,
database: config.database, database: config.database,
timezone: self.sequelize.options.timezone timezone: self.sequelize.options.timezone,
typeCast: function (field, next) {
if (parserMap[field.type]) {
return parserMap[field.type](field);
}
return next();
}
}; };
if (config.dialectOptions) { if (config.dialectOptions) {
......
'use strict'; 'use strict';
var BaseTypes = require('../../data-types') var util = require('util')
, util = require('util')
, _ = require('lodash'); , _ = require('lodash');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.mysql.com/doc/refman/5.7/en/data-types.html'; module.exports = function (BaseTypes) {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.mysql.com/doc/refman/5.7/en/data-types.html';
var UUID = function() { BaseTypes.DATE.types.mysql = ['DATETIME'];
if (!(this instanceof UUID)) return new UUID(); BaseTypes.STRING.types.mysql = ['VAR_STRING'];
BaseTypes.UUID.apply(this, arguments); BaseTypes.CHAR.types.mysql = ['STRING'];
}; BaseTypes.TEXT.types.mysql = ['BLOB'];
util.inherits(UUID, BaseTypes.UUID); BaseTypes.INTEGER.types.mysql = ['LONG'];
BaseTypes.BIGINT.types.mysql = ['BIGINT'];
BaseTypes.FLOAT.types.mysql = ['FLOAT'];
BaseTypes.TIME.types.mysql = ['TIME'];
BaseTypes.DATEONLY.types.mysql = ['DATE'];
BaseTypes.BOOLEAN.types.mysql = ['TINY'];
BaseTypes.BLOB.types.mysql = ['TINYBLOB', 'BLOB', 'LONGBLOB'];
BaseTypes.DECIMAL.types.mysql = ['DECIMAL'];
BaseTypes.UUID.types.mysql = ['UUID'];
BaseTypes.ENUM.types.mysql = ['TEXT']; // Questionable .. overwritten by text
BaseTypes.REAL.types.mysql = ['DOUBLE'];
BaseTypes.DOUBLE.types.mysql = ['DOUBLE'];
UUID.prototype.toSql = function() { var DATE = BaseTypes.DATE.inherits();
return 'CHAR(36) BINARY';
};
var SUPPORTED_GEOMETRY_TYPES = ['POINT', 'LINESTRING', 'POLYGON']; DATE.prototype.$stringify = function (date, options) {
var GEOMETRY = function() { date = BaseTypes.DATE.prototype.$applyTimezone(date, options);
if (!(this instanceof GEOMETRY)) return new GEOMETRY();
BaseTypes.GEOMETRY.apply(this, arguments);
if (_.isEmpty(this.type)) {
this.sqlType = this.key;
} else if (_.includes(SUPPORTED_GEOMETRY_TYPES, this.type)) {
this.sqlType = this.type;
} else {
throw new Error('Supported geometry types are: ' + SUPPORTED_GEOMETRY_TYPES.join(', '));
}
};
util.inherits(GEOMETRY, BaseTypes.GEOMETRY);
GEOMETRY.prototype.toSql = function() { return date.format('YYYY-MM-DD HH:mm:ss');
return this.sqlType; };
};
module.exports = { var UUID = BaseTypes.UUID.inherits();
UUID: UUID,
GEOMETRY: GEOMETRY UUID.prototype.toSql = function() {
}; return 'CHAR(36) BINARY';
};
var SUPPORTED_GEOMETRY_TYPES = ['POINT', 'LINESTRING', 'POLYGON'];
var GEOMETRY = BaseTypes.GEOMETRY.inherits(function() {
if (!(this instanceof GEOMETRY)) return new GEOMETRY();
BaseTypes.GEOMETRY.apply(this, arguments);
_.forIn(module.exports, function (DataType, key) { if (_.isEmpty(this.type)) {
if (!DataType.key) DataType.key = key; this.sqlType = this.key;
if (!DataType.extend) { } else if (_.includes(SUPPORTED_GEOMETRY_TYPES, this.type)) {
DataType.extend = function(oldType) { this.sqlType = this.type;
return new DataType(oldType.options); } else {
}; throw new Error('Supported geometry types are: ' + SUPPORTED_GEOMETRY_TYPES.join(', '));
} }
}); });
util.inherits(GEOMETRY, BaseTypes.GEOMETRY);
GEOMETRY.prototype.toSql = function() {
return this.sqlType;
};
BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY'];
BaseTypes.DATE.types.mysql = ['DATETIME'];
var exports = {
DATE: DATE,
UUID: UUID,
GEOMETRY: GEOMETRY
};
_.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key;
if (!DataType.extend) {
DataType.extend = function(oldType) {
return new DataType(oldType.options);
};
}
});
return exports;
};
...@@ -5,7 +5,7 @@ var _ = require('lodash') ...@@ -5,7 +5,7 @@ var _ = require('lodash')
, ConnectionManager = require('./connection-manager') , ConnectionManager = require('./connection-manager')
, Query = require('./query') , Query = require('./query')
, QueryGenerator = require('./query-generator') , QueryGenerator = require('./query-generator')
, DataTypes = require('./data-types'); , DataTypes = require('../../data-types').mysql;
var MysqlDialect = function(sequelize) { var MysqlDialect = function(sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
......
...@@ -312,23 +312,6 @@ var QueryGenerator = { ...@@ -312,23 +312,6 @@ var QueryGenerator = {
return Utils.addTicks(identifier, '`'); return Utils.addTicks(identifier, '`');
}, },
escape: function(value, field, options) {
options = options || {};
if (value && value._isSequelizeMethod) {
return this.handleSequelizeMethod(value);
} else if (value && field && field.type instanceof DataTypes.GEOMETRY) {
return 'GeomFromText(\'' + Wkt.stringify(value) + '\')';
} else {
if (['INSERT', 'UPDATE'].indexOf(options.context) !== -1 && this.typeValidation && field && field.type && value) {
if (field.type.validate) {
field.type.validate(value);
}
}
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
}
},
/** /**
* Generates an SQL query that returns all foreign keys of a table. * Generates an SQL query that returns all foreign keys of a table.
* *
...@@ -353,7 +336,7 @@ var QueryGenerator = { ...@@ -353,7 +336,7 @@ var QueryGenerator = {
}, },
geometrySelect: function(column) { geometrySelect: function(column) {
return 'AsText(' + this.quoteIdentifiers(column) + ') AS ' + column; return this.quoteIdentifiers(column);
} }
}; };
......
...@@ -5,7 +5,12 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -5,7 +5,12 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise') , Promise = require('../../promise')
, sequelizeErrors = require('../../errors') , sequelizeErrors = require('../../errors')
<<<<<<< HEAD
, semver = require('semver'); , semver = require('semver');
=======
, dataTypes = require('../../data-types').postgres
, _ = require('lodash');
>>>>>>> feat(datatypes) Initial work on datatypes refactor
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -17,10 +22,39 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -17,10 +22,39 @@ ConnectionManager = function(dialect, sequelize) {
} catch (err) { } catch (err) {
throw new Error('Please install \'' + (sequelize.config.dialectModulePath || 'pg') + '\' module manually'); throw new Error('Please install \'' + (sequelize.config.dialectModulePath || 'pg') + '\' module manually');
} }
this.refreshTypeParser(dataTypes);
}; };
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype); Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = function (dataTypes) {
var self = this;
_.each(dataTypes, function (dataType, key) {
if (dataType.parse && dataType.types.postgres) {
if (dataType.types.postgres.oids) {
dataType.types.postgres.oids.forEach(function (oid) {
self.lib.types.setTypeParser(oid, function (value) {
return dataType.parse(value, oid, self.lib.types.getTypeParser);
});
});
}
if (dataType.types.postgres.array_oids) {
dataType.types.postgres.array_oids.forEach(function (oid) {
self.lib.types.setTypeParser(oid, function (value) {
return self.lib.types.arrayParser.create(value, function (value) {
return dataType.parse(value, oid, self.lib.types.getTypeParser);
}).parse();
});
});
}
}
});
};
ConnectionManager.prototype.connect = function(config) { ConnectionManager.prototype.connect = function(config) {
var self = this var self = this
, connectionConfig = {}; , connectionConfig = {};
......
'use strict'; 'use strict';
var BaseTypes = require('../../data-types') /*jshint -W110 */
, util = require('util')
, _ = require('lodash') var _ = require('lodash')
, wkx = require('wkx') , wkx = require('wkx')
, wkt = require('wellknown'); , hstore = require('./hstore');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'http://www.postgresql.org/docs/9.4/static/datatype.html'; module.exports = function (BaseTypes) {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'http://www.postgresql.org/docs/9.4/static/datatype.html';
var STRING = function() { // numeric
if (!(this instanceof STRING)) return new STRING(); BaseTypes.DECIMAL.types.postgres = {
BaseTypes.STRING.apply(this, arguments); oids: [1700],
}; array_oids: [1231]
util.inherits(STRING, BaseTypes.STRING); };
STRING.prototype.toSql = function() { BaseTypes.UUID.types.postgres = {
if (this._binary) { oids: [2950],
return 'BYTEA'; array_oids: [2951]
} };
return BaseTypes.STRING.prototype.toSql.call(this);
};
BaseTypes.TEXT.prototype.toSql = function() { BaseTypes.JSON.types.postgres = {
if (this._length) { oids: [114],
this.warn('PostgreSQL does not support TEXT with options. Plain `TEXT` will be used instead.'); array_oids: [199]
this._length = undefined; };
}
return 'TEXT';
};
var CHAR = function() { BaseTypes.JSONB.types.postgres = {
if (!(this instanceof CHAR)) return new CHAR(); oids: [3802],
BaseTypes.CHAR.apply(this, arguments); array_oids: [3807]
}; };
util.inherits(CHAR, BaseTypes.CHAR);
CHAR.prototype.toSql = function() { BaseTypes.DATEONLY.types.postgres = {
if (this._binary) { oids: [1082],
return 'BYTEA'; array_oids: [1182]
} };
return BaseTypes.CHAR.prototype.toSql.call(this);
};
var BOOLEAN = function() { BaseTypes.TIME.types.postgres = {
if (!(this instanceof BOOLEAN)) return new BOOLEAN(); oids: [1083],
BaseTypes.BOOLEAN.apply(this, arguments); array_oids: [1183]
}; };
util.inherits(BOOLEAN, BaseTypes.BOOLEAN);
BOOLEAN.prototype.toSql = function() { var STRING = BaseTypes.STRING.inherits();
return 'BOOLEAN'; STRING.prototype.toSql = function () {
}; if (this._binary) {
return 'BYTEA';
}
return BaseTypes.STRING.prototype.toSql.call(this);
};
var DATE = function() { BaseTypes.STRING.types.postgres = {
if (!(this instanceof DATE)) return new DATE(); oids: [1043],
BaseTypes.DATE.apply(this, arguments); array_oids: [1015]
}; };
util.inherits(DATE, BaseTypes.DATE);
DATE.prototype.toSql = function() { var TEXT = BaseTypes.TEXT.inherits();
return 'TIMESTAMP WITH TIME ZONE';
};
var INTEGER = function() { TEXT.prototype.toSql = function() {
if (!(this instanceof INTEGER)) return new INTEGER(); if (this._length) {
BaseTypes.INTEGER.apply(this, arguments); this.warn('PostgreSQL does not support TEXT with options. Plain `TEXT` will be used instead.');
this._length = undefined;
// POSTGRES does not support any parameters for integer }
if (this._length || this.options.length || this._unsigned || this._zerofill) { return 'TEXT';
this.warn('PostgreSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.'); };
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(INTEGER, BaseTypes.INTEGER);
var BIGINT = function() {
if (!(this instanceof BIGINT)) return new BIGINT();
BaseTypes.BIGINT.apply(this, arguments);
// POSTGRES does not support any parameters for bigint
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('PostgreSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(BIGINT, BaseTypes.BIGINT);
var REAL = function() {
if (!(this instanceof REAL)) return new REAL();
BaseTypes.REAL.apply(this, arguments);
// POSTGRES does not support any parameters for real
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('PostgreSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(REAL, BaseTypes.REAL);
var DOUBLE = function() {
if (!(this instanceof DOUBLE)) return new DOUBLE();
BaseTypes.DOUBLE.apply(this, arguments);
// POSTGRES does not support any parameters for double
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('PostgreSQL does not support DOUBLE with options. Plain `DOUBLE` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
};
util.inherits(DOUBLE, BaseTypes.DOUBLE);
var FLOAT = function() {
if (!(this instanceof FLOAT)) return new FLOAT();
BaseTypes.FLOAT.apply(this, arguments);
// 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) {
this.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) {
this.warn('PostgreSQL does not support FLOAT unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined;
}
if (this._zerofill) {
this.warn('PostgreSQL does not support FLOAT zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined;
}
};
util.inherits(FLOAT, BaseTypes.FLOAT);
BaseTypes.BLOB.prototype.toSql = function() {
if (this._length) {
this.warn('PostgreSQL does not support BLOB (BYTEA) with options. Plain `BYTEA` will be used instead.');
this._length = undefined;
}
return 'BYTEA';
};
var GEOMETRY = function() { BaseTypes.TEXT.types.postgres = {
if (!(this instanceof GEOMETRY)) return new GEOMETRY(); oids: [25],
BaseTypes.GEOMETRY.apply(this, arguments); array_oids: [1009]
}; };
util.inherits(GEOMETRY, BaseTypes.GEOMETRY);
GEOMETRY.prototype.toSql = function() { var CHAR = BaseTypes.CHAR.inherits();
var result = this.key;
if (this.type){ CHAR.prototype.toSql = function() {
result += '(' + this.type; if (this._binary) {
return 'BYTEA';
}
return BaseTypes.CHAR.prototype.toSql.call(this);
};
BaseTypes.CHAR.types.postgres = {
oids: [18, 1042],
array_oids: [1002, 1014]
};
var BOOLEAN = BaseTypes.BOOLEAN.inherits();
BOOLEAN.prototype.toSql = function() {
return 'BOOLEAN';
};
BaseTypes.BOOLEAN.types.postgres = {
oids: [16],
array_oids: [1000]
};
if (this.srid){ var DATE = BaseTypes.DATE.inherits();
result += ',' + this.srid;
DATE.prototype.toSql = function() {
return 'TIMESTAMP WITH TIME ZONE';
};
BaseTypes.DATE.types.postgres = {
oids: [1184],
array_oids: [1185]
};
var INTEGER = BaseTypes.INTEGER.inherits(function() {
if (!(this instanceof INTEGER)) return new INTEGER();
BaseTypes.INTEGER.apply(this, arguments);
// POSTGRES does not support any parameters for integer
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('PostgreSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
} }
});
result += ')'; // int4
} BaseTypes.INTEGER.types.postgres = {
oids: [23],
array_oids: [1007]
};
return result; var BIGINT = BaseTypes.BIGINT.inherits(function() {
}; if (!(this instanceof BIGINT)) return new BIGINT();
BaseTypes.BIGINT.apply(this, arguments);
GEOMETRY.prototype.parse = function(value) { // POSTGRES does not support any parameters for bigint
var b = new Buffer(value, 'hex'); if (this._length || this.options.length || this._unsigned || this._zerofill) {
return wkt.parse(wkx.Geometry.parse(b).toWkt()); this.warn('PostgreSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
}; this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
});
module.exports = { // int8
STRING: STRING, BaseTypes.BIGINT.types.postgres = {
CHAR: CHAR, oids: [20],
BOOLEAN: BOOLEAN, array_oids: [1016]
DATE: DATE, };
INTEGER: INTEGER,
BIGINT: BIGINT, var REAL = BaseTypes.REAL.inherits(function() {
REAL: REAL, if (!(this instanceof REAL)) return new REAL();
'DOUBLE PRECISION': DOUBLE, BaseTypes.REAL.apply(this, arguments);
FLOAT: FLOAT,
GEOMETRY: GEOMETRY // POSTGRES does not support any parameters for real
}; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('PostgreSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
});
// float4
BaseTypes.REAL.types.postgres = {
oids: [700],
array_oids: [1021]
};
var DOUBLE = BaseTypes.DOUBLE.inherits(function() {
if (!(this instanceof DOUBLE)) return new DOUBLE();
BaseTypes.DOUBLE.apply(this, arguments);
// POSTGRES does not support any parameters for double
if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.warn('PostgreSQL does not support DOUBLE with options. Plain `DOUBLE` will be used instead.');
this._length = undefined;
this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
});
_.forIn(module.exports, function (DataType, key) { // float8
if (!DataType.key) DataType.key = key; BaseTypes.DOUBLE.types.postgres = {
if (!DataType.extend) { oids: [701],
DataType.extend = function(oldType) { array_oids: [1022]
return new DataType(oldType.options); };
};
} var FLOAT = BaseTypes.FLOAT.inherits(function() {
}); if (!(this instanceof FLOAT)) return new FLOAT();
BaseTypes.FLOAT.apply(this, arguments);
// 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) {
this.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) {
this.warn('PostgreSQL does not support FLOAT unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined;
}
if (this._zerofill) {
this.warn('PostgreSQL does not support FLOAT zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined;
}
});
var BLOB = BaseTypes.BLOB.inherits();
BLOB.prototype.toSql = function() {
if (this._length) {
this.warn('PostgreSQL does not support BLOB (BYTEA) with options. Plain `BYTEA` will be used instead.');
this._length = undefined;
}
return 'BYTEA';
};
BLOB.prototype.$stringify = function (value) {
var hex = value.toString('hex');
// bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex + "'";
};
BaseTypes.BLOB.types.postgres = {
oids: [17],
array_oids: [1001]
};
var GEOMETRY = BaseTypes.GEOMETRY.inherits();
GEOMETRY.prototype.toSql = function() {
var result = this.key;
if (this.type){
result += '(' + this.type;
if (this.srid){
result += ',' + this.srid;
}
result += ')';
}
return result;
};
BaseTypes.GEOMETRY.types.postgres = {
oids: [2163225]
};
GEOMETRY.parse = GEOMETRY.prototype.parse = function(value) {
var b = new Buffer(value, 'hex');
return wkx.Geometry.parse(b).toGeoJSON();
};
GEOMETRY.prototype.$stringify = function (value) {
return 'ST_GeomFromGeoJSON(\'' + JSON.stringify(value) + '\')';
};
var HSTORE = BaseTypes.HSTORE;
HSTORE.parse = function (value) {
return hstore.parse(value);
};
BaseTypes.HSTORE.types.postgres = {
oids: [2164508],
array_oids: [2164513]
};
var RANGE = BaseTypes.RANGE;
RANGE.oid_map = {
3904: 1007, // int4
3905: 1007,
3906: 1700, // Numeric
3907: 1700,
3908: 1114, // timestamp
3909: 1114,
3910: 1184, // timestamptz
3911: 1184,
3912: 1082, // date
3913: 1082,
3926: 20, // int8
3927: 20,
};
var range = require('./range');
RANGE.parse = function (value, oid, getTypeParser) {
var parser = getTypeParser(RANGE.oid_map[oid]);
return range.parse(value, parser);
};
RANGE.prototype.escape = false;
RANGE.prototype.$stringify = function (values, options) {
var valuesStringified = values.map(function (value) {
if (this.options.subtype.stringify) {
return this.options.subtype.stringify(value, options);
} else {
return options.escape(value);
}
}, this);
// Array.map does not preserve extra array properties
valuesStringified.inclusive = values.inclusive;
return '\'' + range.stringify(valuesStringified) + '\'';
};
BaseTypes.RANGE.types.postgres = {
oids: [3904, 3906, 3908, 3910, 3912, 3926],
array_oids: [3905, 3907, 3909, 3911, 3913, 3927]
};
var ARRAY = BaseTypes.ARRAY;
BaseTypes.ARRAY.prototype.escape = false;
BaseTypes.ARRAY.prototype.$stringify = function (values, options) {
return 'ARRAY[' + values.map(function (value) {
if (this.type.stringify) {
value = this.type.stringify(value, options);
if (this.type.escape === false) {
return value;
}
}
return options.escape(value);
}, this).join(',') + ']::' + this.toSql();
};
var exports = {
BLOB: BLOB,
STRING: STRING,
CHAR: CHAR,
TEXT: TEXT,
INTEGER: INTEGER,
BOOLEAN: BOOLEAN,
DATE: DATE,
BIGINT: BIGINT,
REAL: REAL,
'DOUBLE PRECISION': DOUBLE,
FLOAT: FLOAT,
GEOMETRY: GEOMETRY,
HSTORE: HSTORE,
RANGE: RANGE,
ARRAY: ARRAY
};
_.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key;
if (!DataType.extend) {
DataType.extend = function(oldType) {
return new DataType(oldType.options);
};
}
});
return exports;
};
...@@ -5,16 +5,13 @@ var _ = require('lodash') ...@@ -5,16 +5,13 @@ var _ = require('lodash')
, ConnectionManager = require('./connection-manager') , ConnectionManager = require('./connection-manager')
, Query = require('./query') , Query = require('./query')
, QueryGenerator = require('./query-generator') , QueryGenerator = require('./query-generator')
, DataTypes = require('./data-types'); , DataTypes = require('../../data-types').postgres;
var PostgresDialect = function(sequelize) { var PostgresDialect = function(sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize); this.connectionManager = new ConnectionManager(this, sequelize);
this.connectionManager.initPools(); this.connectionManager.initPools();
// parseDialectSpecificFields needs access to the pg lib in order to use its array parser. We cannot simply require pg in query.js since the user can specify another library path (such as pg-native etc)
Query.prototype.parseDialectSpecificFields.lib = this.connectionManager.lib;
this.QueryGenerator = _.extend({}, QueryGenerator, { this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options, options: sequelize.options,
_dialect: this, _dialect: this,
......
...@@ -845,48 +845,6 @@ var QueryGenerator = { ...@@ -845,48 +845,6 @@ var QueryGenerator = {
}, },
/* /*
Escape a value (e.g. a string, number or date)
*/
escape: function(value, field, options) {
options = options || {};
if (value && value._isSequelizeMethod) {
return this.handleSequelizeMethod(value);
}
if (['INSERT', 'UPDATE'].indexOf(options.context) !== -1 && this.typeValidation && field && field.type && value) {
if (field.type.validate) {
field.type.validate(value);
}
}
if (Utils._.isObject(value) && field && (field.type instanceof DataTypes.HSTORE || DataTypes.ARRAY.is(field.type, DataTypes.HSTORE))) {
if (field.type instanceof DataTypes.HSTORE){
return "'" + hstore.stringify(value) + "'";
} else if (DataTypes.ARRAY.is(field.type, DataTypes.HSTORE)) {
return 'ARRAY[' + Utils._.map(value, function(v){return "'" + hstore.stringify(v) + "'::hstore";}).join(',') + ']::HSTORE[]';
}
} else if(Utils._.isArray(value) && field && (field.type instanceof DataTypes.RANGE || DataTypes.ARRAY.is(field.type, DataTypes.RANGE))) {
if(field.type instanceof DataTypes.RANGE) { // escape single value
return "'" + range.stringify(value) + "'";
}
else if (DataTypes.ARRAY.is(field.type, DataTypes.RANGE)) { // escape array of ranges
return 'ARRAY[' + Utils._.map(value, function(v){return "'" + range.stringify(v) + "'";}).join(',') + ']::' + field.type.toString();
}
} else if (value!==null && field && field.type instanceof DataTypes.JSON) {
value = JSON.stringify(value);
} else if (Array.isArray(value) && field && DataTypes.ARRAY.is(field.type, DataTypes.JSON)) {
var jsonType = field.type.type; // type may be JSON or JSONB
return 'ARRAY[' + value.map(function (v) {
return SqlString.escape(JSON.stringify(v), false, this.options.timezone, this.dialect, field);
}, this).join(',') + ']::' + jsonType.key + '[]';
} else if (value && field && field.type instanceof DataTypes.GEOMETRY) {
return "ST_GeomFromGeoJSON('" + JSON.stringify(value) + "')";
}
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
},
/** /**
* Generates an SQL query that returns all foreign keys of a table. * Generates an SQL query that returns all foreign keys of a table.
* *
......
...@@ -2,80 +2,17 @@ ...@@ -2,80 +2,17 @@
var Utils = require('../../utils') var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, DataTypes = require('../../data-types')
, hstore = require('./hstore')
, range = require('./range')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
, Promise = require('../../promise') , Promise = require('../../promise')
, sequelizeErrors = require('../../errors.js'); , sequelizeErrors = require('../../errors.js')
, _ = require('lodash');
var parseDialectSpecificFields,
dialectSpecificTypes;
parseDialectSpecificFields = {
// Parses hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT.
hstore: function (value, options) {
if (value === null) return null;
return DataTypes.ARRAY.is(options.dataType, DataTypes.HSTORE) ?
this.lib.types.arrayParser.create(value, hstore.parse).parse() : hstore.parse(value);
},
range: function (value, options) {
if (value === null) return null;
return DataTypes.ARRAY.is(options.dataType, DataTypes.RANGE) ?
this.lib.types.arrayParser.create(value, function (v) {
return range.parse(v, options.dataType.type);
}).parse() : range.parse(value, options.dataType);
},
geometry: function (value, options) {
if (value === null) return null;
return options.dataType.parse(value);
}
};
dialectSpecificTypes = Utils._.keys(parseDialectSpecificFields);
function dialectSpecificFieldDatatypeMap (options, prefix) {
if (!Utils._.isArray(options.types))
return [];
prefix = prefix || '';
var fields = {};
if (options.callee) {
if (options.as)
prefix += options.as + '.';
Utils._.each(options.types, function (type) {
Utils._.each(options.callee['_' + type + 'Attributes'], function (attrName) {
fields[prefix + attrName] = {
dataType: options.callee.attributes[attrName].type || null,
type: type
};
});
});
}
Utils._.each(options.include, function (include) {
fields = Utils._.merge(fields, dialectSpecificFieldDatatypeMap({
callee: include.model,
as: include.as,
include: include.include,
types: options.types
}, prefix));
});
return fields;
}
var Query = function(client, sequelize, options) { var Query = function(client, sequelize, options) {
this.client = client; this.client = client;
this.sequelize = sequelize; this.sequelize = sequelize;
this.instance = options.instance; this.instance = options.instance;
this.model = options.model; this.model = options.model;
this.options = Utils._.extend({ this.options = _.extend({
logging: console.log, logging: console.log,
plain: false, plain: false,
raw: false raw: false
...@@ -156,22 +93,17 @@ Query.prototype.run = function(sql, parameters) { ...@@ -156,22 +93,17 @@ Query.prototype.run = function(sql, parameters) {
}).spread(function(rows, sql, result) { }).spread(function(rows, sql, result) {
var results = rows var results = rows
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0);
, dialectSpecificFields
, isDialectSpecificField = Utils._.memoize(function (key) { return dialectSpecificFields.hasOwnProperty(key); });
if (isTableNameQuery || isRelNameQuery) { if (isRelNameQuery) {
if (isRelNameQuery) { return rows.map(function(row) {
results = rows.map(function(row) { return {
return { name: row.relname,
name: row.relname, tableName: row.relname.split('_')[0]
tableName: row.relname.split('_')[0] };
}; });
}); } else if (isTableNameQuery) {
} else { return rows.map(function(row) { return _.values(row); });
results = rows.map(function(row) { return Utils._.values(row); });
}
return results;
} }
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
...@@ -193,12 +125,11 @@ Query.prototype.run = function(sql, parameters) { ...@@ -193,12 +125,11 @@ Query.prototype.run = function(sql, parameters) {
results.forEach(function (result) { results.forEach(function (result) {
var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',') var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
, field , field
, attribute , attribute
, columns; , columns;
// Map column index in table to column name // Map column index in table to column name
columns = Utils._.zipObject( columns = _.zipObject(
result.column_indexes, result.column_indexes,
self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names) self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
); );
...@@ -246,9 +177,9 @@ Query.prototype.run = function(sql, parameters) { ...@@ -246,9 +177,9 @@ Query.prototype.run = function(sql, parameters) {
// Postgres will treat tables as case-insensitive, so fix the case // Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes // of the returned values to match attributes
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) { if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(self.model.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {}); var attrsMap = _.reduce(self.model.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
rows.forEach(function(row) { rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) { _.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key]; var targetAttr = attrsMap[key];
if (typeof targetAttr === 'string' && targetAttr !== key) { if (typeof targetAttr === 'string' && targetAttr !== key) {
row[targetAttr] = row[key]; row[targetAttr] = row[key];
...@@ -258,25 +189,6 @@ Query.prototype.run = function(sql, parameters) { ...@@ -258,25 +189,6 @@ Query.prototype.run = function(sql, parameters) {
}); });
} }
if (!!self.model && rows.length > 0) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
include: self.options.include,
types: dialectSpecificTypes
});
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
rows.forEach(function (row, rowId, rows) {
Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
});
}
}
return self.handleSelectQuery(rows); return self.handleSelectQuery(rows);
} else if (QueryTypes.DESCRIBE === self.options.type) { } else if (QueryTypes.DESCRIBE === self.options.type) {
result = {}; result = {};
...@@ -320,24 +232,6 @@ Query.prototype.run = function(sql, parameters) { ...@@ -320,24 +232,6 @@ Query.prototype.run = function(sql, parameters) {
return parseInt(result.rowCount, 10); return parseInt(result.rowCount, 10);
} }
if (!!self.model && rows.length > 0) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
types: dialectSpecificTypes
});
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
rows.forEach(function (row, rowId, rows) {
Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
});
}
}
return self.handleSelectQuery(rows); return self.handleSelectQuery(rows);
} else if (QueryTypes.BULKDELETE === self.options.type) { } else if (QueryTypes.BULKDELETE === self.options.type) {
return parseInt(result.rowCount, 10); return parseInt(result.rowCount, 10);
...@@ -345,27 +239,11 @@ Query.prototype.run = function(sql, parameters) { ...@@ -345,27 +239,11 @@ Query.prototype.run = function(sql, parameters) {
return rows[0].sequelize_upsert; return rows[0].sequelize_upsert;
} else if (self.isInsertQuery() || self.isUpdateQuery()) { } else if (self.isInsertQuery() || self.isUpdateQuery()) {
if (self.instance && self.instance.dataValues) { if (self.instance && self.instance.dataValues) {
if (self.model) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
types: dialectSpecificTypes
});
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
Utils._.each(rows[0], function (value, key) {
if (isDialectSpecificField(key))
rows[0][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
}
}
for (var key in rows[0]) { for (var key in rows[0]) {
if (rows[0].hasOwnProperty(key)) { if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]; var record = rows[0][key];
var attr = Utils._.find(self.model.rawAttributes, function (attribute) { var attr = _.find(self.model.rawAttributes, function (attribute) {
return attribute.fieldName === key || attribute.field === key; return attribute.fieldName === key || attribute.field === key;
}); });
...@@ -418,19 +296,19 @@ Query.prototype.formatError = function (err) { ...@@ -418,19 +296,19 @@ Query.prototype.formatError = function (err) {
match = errDetail.replace(/"/g, '').match(/Key \((.*?)\)=\((.*?)\)/); match = errDetail.replace(/"/g, '').match(/Key \((.*?)\)=\((.*?)\)/);
if (match) { if (match) {
fields = Utils._.zipObject(match[1].split(', '), match[2].split(', ')); fields = _.zipObject(match[1].split(', '), match[2].split(', '));
errors = []; errors = [];
message = 'Validation error'; message = 'Validation error';
Utils._.forOwn(fields, function(value, field) { _.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem( errors.push(new sequelizeErrors.ValidationErrorItem(
self.getUniqueConstraintErrorMessage(field), self.getUniqueConstraintErrorMessage(field),
'unique violation', field, value)); 'unique violation', field, value));
}); });
if (this.model && this.model.uniqueKeys) { if (this.model && this.model.uniqueKeys) {
Utils._.forOwn(this.model.uniqueKeys, function(constraint) { _.forOwn(this.model.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) { if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
message = constraint.msg; message = constraint.msg;
return false; return false;
} }
...@@ -455,7 +333,7 @@ Query.prototype.formatError = function (err) { ...@@ -455,7 +333,7 @@ Query.prototype.formatError = function (err) {
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/); match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) { if (match) {
fields = Utils._.zipObject(match[1].split(', '), match[2].split(', ')); fields = _.zipObject(match[1].split(', '), match[2].split(', '));
} }
message = 'Exclusion constraint error'; message = 'Exclusion constraint error';
......
'use strict'; 'use strict';
var Utils = require('../../utils'), var _ = require('lodash');
moment = require('moment');
function stringify (data) { function stringify (data) {
if (data === null) return null; if (data === null) return null;
if (!Utils._.isArray(data) || data.length !== 2) return ''; if (!_.isArray(data) || data.length !== 2) return '';
if (Utils._.any(data, Utils._.isNull)) return ''; if (_.any(data, _.isNull)) return '';
if (data.hasOwnProperty('inclusive')) { if (data.hasOwnProperty('inclusive')) {
if (!data.inclusive) data.inclusive = [false, false]; if (!data.inclusive) data.inclusive = [false, false];
...@@ -17,8 +16,8 @@ function stringify (data) { ...@@ -17,8 +16,8 @@ function stringify (data) {
data.inclusive = [false, false]; data.inclusive = [false, false];
} }
Utils._.each(data, function (value, index) { _.each(data, function (value, index) {
if (Utils._.isObject(value)) { if (_.isObject(value)) {
if (value.hasOwnProperty('inclusive')) data.inclusive[index] = !!value.inclusive; if (value.hasOwnProperty('inclusive')) data.inclusive[index] = !!value.inclusive;
if (value.hasOwnProperty('value')) data[index] = value.value; if (value.hasOwnProperty('value')) data[index] = value.value;
} }
...@@ -27,12 +26,9 @@ function stringify (data) { ...@@ -27,12 +26,9 @@ function stringify (data) {
return (data.inclusive[0] ? '[' : '(') + JSON.stringify(data[0]) + ',' + JSON.stringify(data[1]) + (data.inclusive[1] ? ']' : ')'); return (data.inclusive[0] ? '[' : '(') + JSON.stringify(data[0]) + ',' + JSON.stringify(data[1]) + (data.inclusive[1] ? ']' : ')');
} }
function parse (value, AttributeType) { function parse (value, parser) {
if (value === null) return null; if (value === null) return null;
if(typeof AttributeType === 'function') AttributeType = new AttributeType();
AttributeType = AttributeType || ''; // if attribute is not defined, assign empty string in order to prevent
// AttributeType.toString() to fail with uncaught exception later in the code
var result = value var result = value
.substring(1, value.length - 1) .substring(1, value.length - 1)
.split(',', 2); .split(',', 2);
...@@ -41,18 +37,7 @@ function parse (value, AttributeType) { ...@@ -41,18 +37,7 @@ function parse (value, AttributeType) {
result = result result = result
.map(function (value) { .map(function (value) {
switch (AttributeType.toString()) { return parser(value);
case 'int4range':
return parseInt(value, 10);
case 'numrange':
return parseFloat(value);
case 'daterange':
case 'tsrange':
case 'tstzrange':
return moment(value).toDate();
}
return value;
}); });
result.inclusive = [(value[0] === '['), (value[value.length - 1] === ']')]; result.inclusive = [(value[0] === '['), (value[value.length - 1] === ']')];
......
...@@ -4,6 +4,7 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -4,6 +4,7 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise') , Promise = require('../../promise')
, dataTypes = require('../../data-types').sqlite
, sequelizeErrors = require('../../errors'); , sequelizeErrors = require('../../errors');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
...@@ -20,10 +21,16 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -20,10 +21,16 @@ ConnectionManager = function(dialect, sequelize) {
} catch (err) { } catch (err) {
throw new Error('Please install sqlite3 package manually'); throw new Error('Please install sqlite3 package manually');
} }
this.refreshTypeParser(dataTypes);
}; };
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype); Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = require('./parsing').refresh;
ConnectionManager.prototype.getConnection = function(options) { ConnectionManager.prototype.getConnection = function(options) {
var self = this; var self = this;
options = options || {}; options = options || {};
......
'use strict'; 'use strict';
var BaseTypes = require('../../data-types') var _ = require('lodash');
, util = require('util')
, _ = require('lodash'); module.exports = function (BaseTypes) {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://www.sqlite.org/datatype3.html';
BaseTypes.DATE.types.sqlite = ['DATETIME'];
BaseTypes.STRING.types.sqlite = ['VARCHAR', 'VARCHAR BINARY'];
BaseTypes.CHAR.types.sqlite = ['CHAR', 'CHAR BINARY'];
BaseTypes.TEXT.types.sqlite = ['TEXT'];
BaseTypes.INTEGER.types.sqlite = ['INTEGER'];
BaseTypes.BIGINT.types.sqlite = ['BIGINT'];
BaseTypes.FLOAT.types.sqlite = ['FLOAT'];
BaseTypes.TIME.types.sqlite = ['TIME'];
BaseTypes.DATEONLY.types.sqlite = ['DATE'];
BaseTypes.BOOLEAN.types.sqlite = ['TINYINT'];
BaseTypes.BLOB.types.sqlite = ['TINYBLOB', 'BLOB', 'LONGBLOB'];
BaseTypes.DECIMAL.types.sqlite = ['DECIMAL'];
BaseTypes.UUID.types.sqlite = ['UUID'];
BaseTypes.ENUM.types.sqlite = ['TEXT']; // Questionable .. overwritten by text
BaseTypes.REAL.types.sqlite = ['REAL'];
BaseTypes.DOUBLE.types.sqlite = ['DOUBLE PRECISION'];
var DATE = BaseTypes.DATE.inherits();
DATE.parse = function (date, options) {
if (date.indexOf('+') === -1) {
// For backwards compat. Dates inserted by sequelize < 2.0dev12 will not have a timestamp set
return new Date(date + options.timezone);
} else {
return new Date(date); // We already have a timezone stored in the string
}
};
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://www.sqlite.org/datatype3.html'; var STRING = BaseTypes.STRING.inherits();
STRING.prototype.toSql = function() {
if (this._binary) {
return 'VARCHAR BINARY(' + this._length + ')';
} else {
return BaseTypes.STRING.prototype.toSql.call(this);
}
};
var STRING = function() { var TEXT = BaseTypes.TEXT.inherits();
if (!(this instanceof STRING)) return new STRING(); TEXT.prototype.toSql = function() {
BaseTypes.STRING.apply(this, arguments); if (this._length) {
}; this.warn('SQLite does not support TEXT with options. Plain `TEXT` will be used instead.');
util.inherits(STRING, BaseTypes.STRING); this._length = undefined;
}
STRING.prototype.toSql = function() { return 'TEXT';
if (this._binary) { };
return 'VARCHAR BINARY(' + this._length + ')';
} else {
return BaseTypes.STRING.prototype.toSql.call(this);
}
};
BaseTypes.TEXT.prototype.toSql = function() { var CHAR = BaseTypes.CHAR.inherits();
if (this._length) { CHAR.prototype.toSql = function() {
this.warn('SQLite does not support TEXT with options. Plain `TEXT` will be used instead.'); if (this._binary) {
this._length = undefined; return 'CHAR BINARY(' + this._length + ')';
} } else {
return 'TEXT'; return BaseTypes.CHAR.prototype.toSql.call(this);
}; }
};
var CHAR = function() { var NUMBER = BaseTypes.NUMBER.inherits();
if (!(this instanceof CHAR)) return new CHAR(); NUMBER.prototype.toSql = function() {
BaseTypes.CHAR.apply(this, arguments); var result = this.key;
};
util.inherits(CHAR, BaseTypes.CHAR);
CHAR.prototype.toSql = function() {
if (this._binary) {
return 'CHAR BINARY(' + this._length + ')';
} else {
return BaseTypes.CHAR.prototype.toSql.call(this);
}
};
var NUMBER = function() { if (this._unsigned) {
BaseTypes.NUMBER.apply(this, arguments); result += ' UNSIGNED';
}; }
util.inherits(NUMBER, BaseTypes.NUMBER); if (this._zerofill) {
result += ' ZEROFILL';
NUMBER.prototype.toSql = function() {
var result = this.key;
if (this._unsigned) {
result += ' UNSIGNED';
}
if (this._zerofill) {
result += ' ZEROFILL';
}
if (this._length) {
result += '(' + this._length;
if (typeof this._decimals === 'number') {
result += ',' + this._decimals;
} }
result += ')';
}
return result;
};
var INTEGER = function(length) { if (this._length) {
var options = typeof length === 'object' && length || { result += '(' + this._length;
length: length if (typeof this._decimals === 'number') {
result += ',' + this._decimals;
}
result += ')';
}
return result;
}; };
if (!(this instanceof INTEGER)) return new INTEGER(options);
NUMBER.call(this, options);
};
util.inherits(INTEGER, BaseTypes.INTEGER);
INTEGER.prototype.key = INTEGER.key = 'INTEGER';
INTEGER.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
};
var BIGINT = function(length) { var INTEGER = BaseTypes.INTEGER.inherits(function(length) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length length: length
};
if (!(this instanceof INTEGER)) return new INTEGER(options);
NUMBER.call(this, options);
});
INTEGER.prototype.key = INTEGER.key = 'INTEGER';
INTEGER.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
}; };
if (!(this instanceof BIGINT)) return new BIGINT(options);
NUMBER.call(this, options);
};
util.inherits(BIGINT, BaseTypes.BIGINT);
BIGINT.prototype.key = BIGINT.key = 'BIGINT';
BIGINT.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
};
var FLOAT = function(length, decimals) { var BIGINT = BaseTypes.BIGINT.inherits(function(length) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length
decimals: decimals };
if (!(this instanceof BIGINT)) return new BIGINT(options);
NUMBER.call(this, options);
});
BIGINT.prototype.key = BIGINT.key = 'BIGINT';
BIGINT.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
}; };
if (!(this instanceof FLOAT)) return new FLOAT(options);
NUMBER.call(this, options);
};
util.inherits(FLOAT, BaseTypes.FLOAT);
FLOAT.prototype.key = FLOAT.key = 'FLOAT';
FLOAT.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
};
var DOUBLE = function(length, decimals) { var FLOAT = BaseTypes.FLOAT.inherits(function(length, decimals) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
decimals: decimals decimals: decimals
};
if (!(this instanceof FLOAT)) return new FLOAT(options);
NUMBER.call(this, options);
});
FLOAT.prototype.key = FLOAT.key = 'FLOAT';
FLOAT.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
}; };
if (!(this instanceof DOUBLE)) return new DOUBLE(options);
NUMBER.call(this, options);
};
util.inherits(DOUBLE, BaseTypes.DOUBLE);
DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION';
DOUBLE.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
};
var REAL = function(length, decimals) { var DOUBLE = BaseTypes.DOUBLE.inherits(function(length, decimals) {
var options = typeof length === 'object' && length || { var options = typeof length === 'object' && length || {
length: length, length: length,
decimals: decimals decimals: decimals
};
if (!(this instanceof DOUBLE)) return new DOUBLE(options);
NUMBER.call(this, options);
});
DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION';
DOUBLE.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
}; };
if (!(this instanceof REAL)) return new REAL(options);
NUMBER.call(this, options);
};
util.inherits(REAL, BaseTypes.REAL);
REAL.prototype.key = REAL.key = 'REAL';
REAL.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
};
module.exports = { var REAL = BaseTypes.REAL.inherits(function(length, decimals) {
STRING: STRING, var options = typeof length === 'object' && length || {
CHAR: CHAR, length: length,
NUMBER: NUMBER, decimals: decimals
FLOAT: FLOAT,
REAL: REAL,
'DOUBLE PRECISION': DOUBLE,
INTEGER: INTEGER,
BIGINT: BIGINT
};
_.forIn(module.exports, function (DataType, key) {
if (!DataType.key) DataType.key = key;
if (!DataType.extend) {
DataType.extend = function(oldType) {
return new DataType(oldType.options);
}; };
} if (!(this instanceof REAL)) return new REAL(options);
}); NUMBER.call(this, options);
\ No newline at end of file });
REAL.prototype.key = REAL.key = 'REAL';
REAL.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this);
};
var exports = {
DATE: DATE,
STRING: STRING,
CHAR: CHAR,
NUMBER: NUMBER,
FLOAT: FLOAT,
REAL: REAL,
'DOUBLE PRECISION': DOUBLE,
INTEGER: INTEGER,
BIGINT: BIGINT,
TEXT: TEXT
};
_.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key;
if (!DataType.extend) {
DataType.extend = function(oldType) {
return new DataType(oldType.options);
};
}
});
return exports;
};
...@@ -5,7 +5,7 @@ var _ = require('lodash') ...@@ -5,7 +5,7 @@ var _ = require('lodash')
, ConnectionManager = require('./connection-manager') , ConnectionManager = require('./connection-manager')
, Query = require('./query') , Query = require('./query')
, QueryGenerator = require('./query-generator') , QueryGenerator = require('./query-generator')
, DataTypes = require('./data-types'); , DataTypes = require('../../data-types').sqlite;
var SqliteDialect = function(sequelize) { var SqliteDialect = function(sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
......
'use strict';
var _ = require('lodash');
module.exports.parsers = {};
module.exports.refresh = function (dataTypes) {
_.each(dataTypes, function (dataType, key) {
if (dataType.parse && dataType.types.sqlite) {
dataType.types.sqlite.forEach(function (type) {
module.exports.parsers[type] = dataType.parse;
});
}
});
};
...@@ -195,7 +195,7 @@ var QueryGenerator = { ...@@ -195,7 +195,7 @@ var QueryGenerator = {
if (Utils._.isObject(dataType)) { if (Utils._.isObject(dataType)) {
var template = '<%= type %>' var template = '<%= type %>'
, replacements = { type: dataType.type }; , replacements = { type: dataType.type.toString() };
if (dataType.type instanceof DataTypes.ENUM) { if (dataType.type instanceof DataTypes.ENUM) {
replacements.type = 'TEXT'; replacements.type = 'TEXT';
......
'use strict'; 'use strict';
var Utils = require('../../utils') var Utils = require('../../utils')
, _ = require('lodash')
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
, sequelizeErrors = require('../../errors.js'); , sequelizeErrors = require('../../errors.js')
, parsers = require('./parsing').parsers;
var Query = function(database, sequelize, options) { var Query = function(database, sequelize, options) {
this.database = database; this.database = database;
this.sequelize = sequelize; this.sequelize = sequelize;
this.instance = options.instance; this.instance = options.instance;
this.model = options.model; this.model = options.model;
this.options = Utils._.extend({ this.options = _.extend({
logging: console.log, logging: console.log,
plain: false, plain: false,
raw: false raw: false
...@@ -47,6 +49,28 @@ Query.formatBindParameters = function(sql, values, dialect) { ...@@ -47,6 +49,28 @@ Query.formatBindParameters = function(sql, values, dialect) {
return [sql, bindParam]; return [sql, bindParam];
}; };
Query.prototype.$collectModels = function(include, prefix) {
var ret = {};
if (include) {
include.forEach(function (include) {
var key;
if (!prefix) {
key = include.as;
} else {
key = prefix + '.' + include.as;
}
ret[key] = include.model;
if (include.include) {
_.merge(ret, this.$collectModels(include.include, key));
}
}, this);
}
return ret;
};
Query.prototype.run = function(sql, parameters) { Query.prototype.run = function(sql, parameters) {
var self = this var self = this
, promise; , promise;
...@@ -93,29 +117,49 @@ Query.prototype.run = function(sql, parameters) { ...@@ -93,29 +117,49 @@ Query.prototype.run = function(sql, parameters) {
result = results.map(function(resultSet) { return resultSet.name; }); result = results.map(function(resultSet) { return resultSet.name; });
} else if (self.isSelectQuery()) { } else if (self.isSelectQuery()) {
if (!self.options.raw) { if (!self.options.raw) {
// This is a map of prefix strings to models, e.g. user.projects -> Project model
var prefixes = self.$collectModels(self.options.include);
results = results.map(function(result) { results = results.map(function(result) {
for (var name in result) { return _.mapValues(result, function (value, name) {
if (result.hasOwnProperty(name) && metaData.columnTypes[name]) { var model;
if (metaData.columnTypes[name] === 'DATETIME') { if (name.indexOf('.') !== -1) {
// we need to convert the timestamps into actual date objects var lastind = name.lastIndexOf('.');
var val = result[name];
model = prefixes[name.substr(0, lastind)];
if (val !== null) {
if (val.indexOf('+') === -1) { name = name.substr(lastind + 1);
// For backwards compat. Dates inserted by sequelize < 2.0dev12 will not have a timestamp set } else {
result[name] = new Date(val + self.sequelize.options.timezone); model = self.options.model;
} else { }
result[name] = new Date(val); // We already have a timezone stored in the string
} var tableTypes = metaData.columnTypes[model.tableName];
}
} else if (metaData.columnTypes[name].lastIndexOf('BLOB') !== -1) { if (!(name in tableTypes)) {
if (result[name]) { // The column is aliased
result[name] = new Buffer(result[name]); _.forOwn(model.rawAttributes, function (attribute, key) {
if (name === key && attribute.field) {
name = attribute.field;
return false;
} }
} });
}
var type = tableTypes[name];
if (type.indexOf('(') !== -1) {
// Remove the lenght part
type = type.substr(0, type.indexOf('('));
} }
} type = type.replace('UNSIGNED', '').replace('ZEROFILL', '');
return result; type = type.trim();
var parse = parsers[type];
if (value !== null && parse) {
return parse(value, { timezone: self.sequelize.options.timezone});
}
return value;
});
}); });
} }
...@@ -192,15 +236,20 @@ Query.prototype.run = function(sql, parameters) { ...@@ -192,15 +236,20 @@ Query.prototype.run = function(sql, parameters) {
if (!tableNames.length) { if (!tableNames.length) {
return executeSql(); return executeSql();
} else { } else {
// If we already have the metadata for the table, there no need to ask for it again
tableNames = _.filter(tableNames, function (tableName) {
return !(tableName in columnTypes);
});
return Utils.Promise.map(tableNames, function(tableName) { return Utils.Promise.map(tableNames, function(tableName) {
if (tableName !== 'sqlite_master') { if (tableName !== 'sqlite_master') {
return new Utils.Promise(function(resolve) { return new Utils.Promise(function(resolve) {
// get the column types columnTypes[tableName] = {};
self.database.all('PRAGMA table_info(`' + tableName + '`)', function(err, results) { self.database.all('PRAGMA table_info(`' + tableName + '`)', function(err, results) {
if (!err) { if (!err) {
for (var i = 0, l = results.length; i < l; i++) { results.forEach(function (result) {
columnTypes[tableName + '.' + results[i].name] = columnTypes[results[i].name] = results[i].type; columnTypes[tableName][result.name] = result.type;
} });
} }
resolve(); resolve();
}); });
...@@ -257,8 +306,8 @@ Query.prototype.formatError = function (err) { ...@@ -257,8 +306,8 @@ Query.prototype.formatError = function (err) {
}); });
if (this.model) { if (this.model) {
Utils._.forOwn(this.model.uniqueKeys, function(constraint) { _.forOwn(this.model.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, fields) && !!constraint.msg) { if (_.isEqual(constraint.fields, fields) && !!constraint.msg) {
message = constraint.msg; message = constraint.msg;
return false; return false;
} }
......
...@@ -428,6 +428,10 @@ Sequelize.prototype.InvalidConnectionError = Sequelize.InvalidConnectionError = ...@@ -428,6 +428,10 @@ Sequelize.prototype.InvalidConnectionError = Sequelize.InvalidConnectionError =
Sequelize.prototype.ConnectionTimedOutError = Sequelize.ConnectionTimedOutError = Sequelize.prototype.ConnectionTimedOutError = Sequelize.ConnectionTimedOutError =
sequelizeErrors.ConnectionTimedOutError; sequelizeErrors.ConnectionTimedOutError;
Sequelize.prototype.refreshTypes = function () {
this.connectionManager.refreshTypeParser(DataTypes);
};
/** /**
* Returns the specified dialect. * Returns the specified dialect.
* *
......
'use strict'; 'use strict';
/* jshint -W110 */ /* jshint -W110 */
var moment = require('moment-timezone') var dataTypes = require('./data-types')
, isArrayBufferView
, SqlString = exports; , SqlString = exports;
if (typeof ArrayBufferView === 'function') {
isArrayBufferView = function(object) { return object && (object instanceof ArrayBufferView); };
} else {
var arrayBufferViews = [
Int8Array, Uint8Array, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
];
isArrayBufferView = function(object) {
for (var i = 0; i < 8; i++) {
if (object instanceof arrayBufferViews[i]) {
return true;
}
}
return false;
};
}
SqlString.escapeId = function(val, forbidQualified) { SqlString.escapeId = function(val, forbidQualified) {
if (forbidQualified) { if (forbidQualified) {
return '`' + val.replace(/`/g, '``') + '`'; return '`' + val.replace(/`/g, '``') + '`';
...@@ -30,19 +11,7 @@ SqlString.escapeId = function(val, forbidQualified) { ...@@ -30,19 +11,7 @@ SqlString.escapeId = function(val, forbidQualified) {
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'; return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
}; };
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { SqlString.escape = function(val, timeZone, dialect) {
if (arguments.length === 1 && typeof val === 'object' && val !== null) {
val = val.val || val.value || null;
stringifyObjects = val.stringifyObjects || val.objects || undefined;
timeZone = val.timeZone || val.zone || null;
dialect = val.dialect || null;
field = val.field || null;
}
else if (arguments.length === 2 && typeof stringifyObjects === 'object' && stringifyObjects !== null) {
timeZone = stringifyObjects.timeZone || stringifyObjects.zone || null;
dialect = stringifyObjects.dialect || null;
field = stringifyObjects.field || null;
}
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
...@@ -60,22 +29,11 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -60,22 +29,11 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
} }
if (val instanceof Date) { if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || 'Z', dialect); val = dataTypes[dialect].DATE.prototype.stringify(val, { options: timeZone });
} }
if (Buffer.isBuffer(val)) { if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val, dialect); return dataTypes[dialect].BLOB.prototype.stringify(val);
}
if (Array.isArray(val) || isArrayBufferView(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field);
}
if (typeof val === 'object' && val !== null) {
if (stringifyObjects) {
val = val.toString();
} else {
return SqlString.objectToValues(val, timeZone);
}
} }
if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') {
...@@ -98,43 +56,6 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -98,43 +56,6 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
return "'" + val + "'"; return "'" + val + "'";
}; };
SqlString.arrayToList = function(array, timeZone, dialect, field) {
var valstr, i;
if (dialect === 'postgres') {
valstr = '';
if (array.map) {
valstr = array.map(function(v) {
return SqlString.escape(v, true, timeZone, dialect, field);
}).join(',');
} else {
for (i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect, field) + ',';
}
valstr = valstr.slice(0, -1);
}
var ret = 'ARRAY[' + valstr + ']';
if (!!field && !!field.type) {
ret += '::' + field.type.toSql().replace(/\(\d+\)/g, '');
}
return ret;
} else {
if (array.map) {
return array.map(function(v) {
if (Array.isArray(v)) {
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')';
}
return SqlString.escape(v, true, timeZone, dialect);
}).join(', ');
} else {
valstr = '';
for (i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect) + ', ';
}
return valstr.slice(0, -2);
}
}
};
SqlString.format = function(sql, values, timeZone, dialect) { SqlString.format = function(sql, values, timeZone, dialect) {
values = [].concat(values); values = [].concat(values);
...@@ -143,7 +64,7 @@ SqlString.format = function(sql, values, timeZone, dialect) { ...@@ -143,7 +64,7 @@ SqlString.format = function(sql, values, timeZone, dialect) {
return match; return match;
} }
return SqlString.escape(values.shift(), false, timeZone, dialect); return SqlString.escape(values.shift(), timeZone, dialect);
}); });
}; };
...@@ -154,51 +75,9 @@ SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) { ...@@ -154,51 +75,9 @@ SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
} }
if (values[key] !== undefined) { if (values[key] !== undefined) {
return SqlString.escape(values[key], false, timeZone, dialect); return SqlString.escape(values[key], timeZone, dialect);
} else { } else {
throw new Error('Named parameter "' + value + '" has no value in the given object.'); throw new Error('Named parameter "' + value + '" has no value in the given object.');
} }
}); });
}; };
SqlString.dateToString = function(date, timeZone, dialect) {
if (moment.tz.zone(timeZone)) {
date = moment(date).tz(timeZone);
} else {
date = moment(date).utcOffset(timeZone);
}
if (dialect === 'mysql' || dialect === 'mariadb') {
return date.format('YYYY-MM-DD HH:mm:ss');
} else {
// ZZ here means current timezone, _not_ UTC
return date.format('YYYY-MM-DD HH:mm:ss.SSS Z');
}
};
SqlString.bufferToString = function(buffer, dialect) {
var hex = buffer.toString('hex');
if (dialect === 'postgres') {
// bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex + "'";
} else if (dialect === 'mssql') {
return '0x' + hex;
}
return "X'" + hex + "'";
};
SqlString.objectToValues = function(object, timeZone) {
var values = [];
for (var key in object) {
var value = object[key];
if (typeof value === 'function') {
continue;
}
values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone));
}
return values.join(', ');
};
'use strict';
/* jshint -W030 */
/* jshint -W110 */
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + '/../../lib/data-types')
, dialect = Support.getTestDialect()
, sinon = require('sinon')
, _ = require('lodash')
, moment = require('moment')
, current = Support.sequelize;
describe.only(Support.getTestDialectTeaser('DataTypes'), function() {
it('allows me to use a custom parse function', function () {
// var parseStub = sinon.stub(this.sequelize.DATE, 'parse', function (value) {
// new moment(value);
// });
var parse = Sequelize.DATE.parse = sinon.spy(function (value) {
return moment(value, 'YYYY-MM-DD HH:mm:ss Z');
});
var stringify = Sequelize.DATE.prototype.stringify = sinon.spy(function (value, options) {
if (!moment.isMoment(value)) {
value = this.$applyTimezone(value, options);
}
return value.format('YYYY-MM-DD HH:mm:ss Z');
});
this.sequelize.refreshTypes();
var User = this.sequelize.define('user', {
dateField: Sequelize.DATE
}, {
timestamps: false
});
return this.sequelize.sync({ force: true }).then(function () {
return User.create({
dateField: moment("2011 10 31", 'YYYY MM DD')
});
}).then(function () {
return User.findAll().get(0);
}).then(function (user) {
expect(parse).to.have.been.called;
expect(stringify).to.have.been.called;
expect(moment.isMoment(user.dateField)).to.be.ok;
});
});
var testType = function (Type, value) {
it((new Type()).toSql(), function () {
var parse = Type.parse = sinon.spy(function (value) {
return value;
});
var stringify = Type.prototype.stringify = sinon.spy(function (value) {
return value;
});
this.sequelize.refreshTypes();
var User = this.sequelize.define('user', {
field: new Type()
}, {
timestamps: false
});
return this.sequelize.sync({ force: true }).then(function () {
return User.create({
field: value
});
}).then(function () {
return User.findAll().get(0);
}).then(function (user) {
expect(parse).to.have.been.called;
expect(stringify).to.have.been.called;
});
});
};
[Sequelize.TEXT, Sequelize.STRING, Sequelize.CHAR].forEach(function (Type) {
testType(Type, 'foobar');
});
[Sequelize.BOOLEAN, Sequelize.DOUBLE, Sequelize.REAL, Sequelize.INTEGER].forEach(function (Type) {
testType(Type, 1);
});
});
...@@ -659,8 +659,8 @@ if (dialect.match(/^postgres/)) { ...@@ -659,8 +659,8 @@ if (dialect.match(/^postgres/)) {
return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) { return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) {
// Check to see if the default value for a range field works // Check to see if the default value for a range field works
expect(newUser.acceptable_marks.length).to.equal(2); expect(newUser.acceptable_marks.length).to.equal(2);
expect(newUser.acceptable_marks[0]).to.equal(0.65); // lower bound expect(newUser.acceptable_marks[0]).to.equal('0.65'); // lower bound
expect(newUser.acceptable_marks[1]).to.equal(1); // upper bound expect(newUser.acceptable_marks[1]).to.equal('1'); // upper bound
expect(newUser.acceptable_marks.inclusive).to.deep.equal([false, false]); // not inclusive expect(newUser.acceptable_marks.inclusive).to.deep.equal([false, false]); // not inclusive
expect(newUser.course_period[0] instanceof Date).to.be.ok; // lower bound expect(newUser.course_period[0] instanceof Date).to.be.ok; // lower bound
expect(newUser.course_period[1] instanceof Date).to.be.ok; // upper bound expect(newUser.course_period[1] instanceof Date).to.be.ok; // upper bound
...@@ -731,8 +731,8 @@ if (dialect.match(/^postgres/)) { ...@@ -731,8 +731,8 @@ if (dialect.match(/^postgres/)) {
return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(function(newUser) { return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(function(newUser) {
// Check to see if the default value for a range field works // Check to see if the default value for a range field works
expect(newUser.acceptable_marks.length).to.equal(2); expect(newUser.acceptable_marks.length).to.equal(2);
expect(newUser.acceptable_marks[0]).to.equal(0.65); // lower bound expect(newUser.acceptable_marks[0]).to.equal('0.65'); // lower bound
expect(newUser.acceptable_marks[1]).to.equal(1); // upper bound expect(newUser.acceptable_marks[1]).to.equal('1'); // upper bound
expect(newUser.acceptable_marks.inclusive).to.deep.equal([false, false]); // not inclusive expect(newUser.acceptable_marks.inclusive).to.deep.equal([false, false]); // not inclusive
expect(newUser.course_period[0] instanceof Date).to.be.ok; expect(newUser.course_period[0] instanceof Date).to.be.ok;
expect(newUser.course_period[1] instanceof Date).to.be.ok; expect(newUser.course_period[1] instanceof Date).to.be.ok;
......
...@@ -846,7 +846,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -846,7 +846,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
logging: function(sql) { logging: function(sql) {
test = true; test = true;
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1); expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1);
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1); expect(sql.indexOf('ARRAY[]::VARCHAR(255)[]')).to.be.above(-1);
} }
}); });
}).then(function() { }).then(function() {
...@@ -874,7 +874,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -874,7 +874,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
logging: function(sql) { logging: function(sql) {
test = true; test = true;
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1); expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1);
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1); expect(sql.indexOf('ARRAY[]::VARCHAR(255)[]')).to.be.above(-1);
} }
}); });
}); });
......
...@@ -312,4 +312,4 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -312,4 +312,4 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
}); });
} }
}); });
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!