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

Commit 6e13324f by Mick Hansen

Merge pull request #4186 from sequelize/datatypes

WIP: Datatypes system refactor
2 parents 1b8d2ade 8879669a
...@@ -19,6 +19,7 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -19,6 +19,7 @@ ConnectionManager = function(dialect, sequelize) {
this.config = config; this.config = config;
this.dialect = dialect; this.dialect = dialect;
this.versionPromise = null; this.versionPromise = null;
this.dialectName = this.sequelize.options.dialect;
if (config.pool) { if (config.pool) {
config.pool = _.clone(config.pool); // Make sure we don't modify the existing config object (user might re-use it) config.pool = _.clone(config.pool); // Make sure we don't modify the existing config object (user might re-use it)
...@@ -44,6 +45,23 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -44,6 +45,23 @@ ConnectionManager = function(dialect, sequelize) {
process.on('exit', this.onProcessExit); process.on('exit', this.onProcessExit);
}; };
ConnectionManager.prototype.refreshTypeParser = function(dataTypes) {
_.each(dataTypes, function (dataType, key) {
if (dataType.hasOwnProperty('parse')) {
var dialectName = this.dialectName;
if (dialectName === 'mariadb') {
dialectName = 'mysql';
}
if (dataType.types[dialectName]) {
this.$refreshTypeParser(dataType);
} else {
throw new Error('Parse function not supported for type ' + dataType.key + ' in dialect ' + this.dialectName);
}
}
}, this);
};
ConnectionManager.prototype.onProcessExit = function() { ConnectionManager.prototype.onProcessExit = function() {
var self = this; var self = this;
......
...@@ -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) {
if (value) {
if (value._isSequelizeMethod) {
return this.handleSequelizeMethod(value); return this.handleSequelizeMethod(value);
} else { } else {
if (field && field.type) {
if (['INSERT', 'UPDATE'].indexOf(options.context) !== -1 && this.typeValidation && field && field.type && value) { if (['INSERT', 'UPDATE'].indexOf(options.context) !== -1 && this.typeValidation && field && field.type && value) {
if (field.type.validate) { if (field.type.validate) {
field.type.validate(value); field.type.validate(value);
} }
} }
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
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);
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, this.options.timezone, this.dialect);
}, },
/** /**
...@@ -1048,10 +1066,6 @@ var QueryGenerator = { ...@@ -1048,10 +1066,6 @@ var QueryGenerator = {
return self.handleSequelizeMethod(attr); return self.handleSequelizeMethod(attr);
} }
if (mainModel && mainModel._isGeometryAttribute(attr)) {
attr = self.geometrySelect(attr);
}
if (Array.isArray(attr) && attr.length === 2) { if (Array.isArray(attr) && attr.length === 2) {
attr = attr.slice(); attr = attr.slice();
...@@ -1107,7 +1121,6 @@ var QueryGenerator = { ...@@ -1107,7 +1121,6 @@ var QueryGenerator = {
// includeIgnoreAttributes is used by aggregate functions // includeIgnoreAttributes is used by aggregate functions
if (options.includeIgnoreAttributes !== false) { if (options.includeIgnoreAttributes !== false) {
attributes = include.attributes.map(function(attr) { attributes = include.attributes.map(function(attr) {
var attrAs = attr, var attrAs = attr,
verbatim = false; verbatim = false;
...@@ -2291,10 +2304,6 @@ var QueryGenerator = { ...@@ -2291,10 +2304,6 @@ var QueryGenerator = {
booleanValue: function(value) { booleanValue: function(value) {
return value; return value;
},
geometrySelect: function(column) {
return this.quoteIdentifiers(column);
} }
}; };
......
...@@ -4,7 +4,8 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -4,7 +4,8 @@ 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')
, parserStore = require('../parserStore')('mssql');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -20,6 +21,16 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -20,6 +21,16 @@ ConnectionManager = function(dialect, sequelize) {
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 (dataType) {
parserStore.refresh(dataType);
};
ConnectionManager.prototype.$clearTypeParser = function () {
parserStore.clear();
};
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) {
......
'use strict'; 'use strict';
var BaseTypes = require('../../data-types') var _ = require('lodash');
, util = require('util')
, _ = require('lodash'); module.exports = function (BaseTypes) {
var warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx');
BaseTypes.DATE.types.mssql = [42];
BaseTypes.STRING.types.mssql = [231, 173];
BaseTypes.CHAR.types.mssql = [175];
BaseTypes.TEXT.types.mssql = false;
BaseTypes.INTEGER.types.mssql = [38];
BaseTypes.BIGINT.types.mssql = false;
BaseTypes.FLOAT.types.mssql = [109];
BaseTypes.TIME.types.mssql = [41];
BaseTypes.DATEONLY.types.mssql = [40];
BaseTypes.BOOLEAN.types.mssql = [104];
BaseTypes.BLOB.types.mssql = [165];
BaseTypes.DECIMAL.types.mssql = [106];
BaseTypes.UUID.types.mssql = false;
BaseTypes.ENUM.types.mssql = false;
BaseTypes.REAL.types.mssql = [109];
BaseTypes.DOUBLE.types.mssql = [109];
// BaseTypes.GEOMETRY.types.mssql = [240]; // not yet supported
BaseTypes.GEOMETRY.types.mssql = false;
var BLOB = BaseTypes.BLOB.inherits();
BLOB.prototype.toSql = function() {
if (this._length) {
if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8
warn('MSSQL does not support BLOB with the `length` = `tiny` option. `VARBINARY(256)` will be used instead.');
return 'VARBINARY(256)';
}
warn('MSSQL does not support BLOB with the `length` option. `VARBINARY(MAX)` will be used instead.');
}
return 'VARBINARY(MAX)';
};
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx'; BLOB.prototype.$hexify = function (hex) {
return '0x' + hex;
};
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() { STRING.prototype.toSql = function() {
if (!this._binary) { if (!this._binary) {
return 'NVARCHAR(' + this._length + ')'; return 'NVARCHAR(' + this._length + ')';
} else{ } else{
return 'BINARY(' + this._length + ')'; return 'BINARY(' + this._length + ')';
} }
}; };
STRING.prototype.escape = false;
STRING.prototype.$stringify = function (value, options) {
if (this._binary) {
return BLOB.prototype.$stringify(value);
} else {
return options.escape(value);
}
};
var TEXT = BaseTypes.TEXT.inherits();
BaseTypes.TEXT.prototype.toSql = function() { TEXT.prototype.toSql = function() {
// TEXT is deprecated in mssql and it would normally be saved as a non-unicode string. // TEXT is deprecated in mssql and it would normally be saved as a non-unicode string.
// Using unicode is just future proof // Using unicode is just future proof
if (this._length) { if (this._length) {
if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8 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.'); warn('MSSQL does not support TEXT with the `length` = `tiny` option. `NVARCHAR(256)` will be used instead.');
return 'NVARCHAR(256)'; return 'NVARCHAR(256)';
} }
this.warn('MSSQL does not support TEXT with the `length` option. `NVARCHAR(MAX)` will be used instead.'); warn('MSSQL does not support TEXT with the `length` option. `NVARCHAR(MAX)` will be used instead.');
} }
return 'NVARCHAR(MAX)'; 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() {
if (this._length) {
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.');
}
return 'VARBINARY(MAX)';
};
var UUID = function() { var UUID = BaseTypes.UUID.inherits();
if (!(this instanceof UUID)) return new UUID();
BaseTypes.UUID.apply(this, arguments);
};
util.inherits(UUID, BaseTypes.UUID);
UUID.prototype.toSql = function() { UUID.prototype.toSql = function() {
return 'CHAR(36)'; return 'CHAR(36)';
}; };
var NOW = function() { var NOW = BaseTypes.NOW.inherits();
if (!(this instanceof NOW)) return new NOW();
BaseTypes.NOW.apply(this, arguments);
};
util.inherits(NOW, BaseTypes.NOW);
NOW.prototype.toSql = function() { NOW.prototype.toSql = function() {
return 'GETDATE()'; return 'GETDATE()';
}; };
var DATE = function() { var DATE = BaseTypes.DATE.inherits();
if (!(this instanceof DATE)) return new DATE();
BaseTypes.DATE.apply(this, arguments);
};
util.inherits(DATE, BaseTypes.DATE);
DATE.prototype.toSql = function() { DATE.prototype.toSql = function() {
return 'DATETIME2'; return 'DATETIME2';
}; };
var INTEGER = function() { var INTEGER = BaseTypes.INTEGER.inherits(function() {
if (!(this instanceof INTEGER)) return new INTEGER(); if (!(this instanceof INTEGER)) return new INTEGER();
BaseTypes.INTEGER.apply(this, arguments); BaseTypes.INTEGER.apply(this, arguments);
// MSSQL does not support any options for integer // MSSQL does not support any options for integer
if (this._length || this.options.length || this._unsigned || this._zerofill) { 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.'); warn('MSSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.');
this._length = undefined; this._length = undefined;
this.options.length = undefined; this.options.length = undefined;
this._unsigned = undefined; this._unsigned = undefined;
this._zerofill = undefined; this._zerofill = undefined;
} }
}; });
util.inherits(INTEGER, BaseTypes.INTEGER);
var BIGINT = function() { var BIGINT = BaseTypes.BIGINT.inherits(function() {
if (!(this instanceof BIGINT)) return new BIGINT(); if (!(this instanceof BIGINT)) return new BIGINT();
BaseTypes.BIGINT.apply(this, arguments); BaseTypes.BIGINT.apply(this, arguments);
// MSSQL does not support any options for bigint // MSSQL does not support any options for bigint
if (this._length || this.options.length || this._unsigned || this._zerofill) { 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.'); warn('MSSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
this._length = undefined; this._length = undefined;
this.options.length = undefined; this.options.length = undefined;
this._unsigned = undefined; this._unsigned = undefined;
this._zerofill = undefined; this._zerofill = undefined;
} }
}; });
util.inherits(BIGINT, BaseTypes.BIGINT);
var REAL = function() { var REAL = BaseTypes.REAL.inherits(function() {
if (!(this instanceof REAL)) return new REAL(); if (!(this instanceof REAL)) return new REAL();
BaseTypes.REAL.apply(this, arguments); BaseTypes.REAL.apply(this, arguments);
// MSSQL does not support any options for real // MSSQL does not support any options for real
if (this._length || this.options.length || this._unsigned || this._zerofill) { 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.'); warn('MSSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._length = undefined; this._length = undefined;
this.options.length = undefined; this.options.length = undefined;
this._unsigned = undefined; this._unsigned = undefined;
this._zerofill = undefined; this._zerofill = undefined;
} }
}; });
util.inherits(REAL, BaseTypes.REAL);
var FLOAT = function() { var FLOAT = BaseTypes.FLOAT.inherits(function() {
if (!(this instanceof FLOAT)) return new FLOAT(); if (!(this instanceof FLOAT)) return new FLOAT();
BaseTypes.FLOAT.apply(this, arguments); BaseTypes.FLOAT.apply(this, arguments);
...@@ -138,27 +150,29 @@ var FLOAT = function() { ...@@ -138,27 +150,29 @@ var FLOAT = function() {
// Values between 25-53 result in 15 digits precision (8 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 decimals are provided remove these and print a warning
if (this._decimals) { if (this._decimals) {
this.warn('MSSQL does not support Float with decimals. Plain `FLOAT` will be used instead.'); warn('MSSQL does not support Float with decimals. Plain `FLOAT` will be used instead.');
this._length = undefined; this._length = undefined;
this.options.length = undefined; this.options.length = undefined;
} }
if (this._unsigned) { if (this._unsigned) {
this.warn('MSSQL does not support Float unsigned. `UNSIGNED` was removed.'); warn('MSSQL does not support Float unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined; this._unsigned = undefined;
} }
if (this._zerofill) { if (this._zerofill) {
this.warn('MSSQL does not support Float zerofill. `ZEROFILL` was removed.'); warn('MSSQL does not support Float zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined; this._zerofill = undefined;
} }
}; });
util.inherits(FLOAT, BaseTypes.FLOAT);
BaseTypes.ENUM.prototype.toSql = function() { var ENUM = BaseTypes.ENUM.inherits();
ENUM.prototype.toSql = function() {
return 'VARCHAR(255)'; return 'VARCHAR(255)';
}; };
module.exports = { var exports = {
BLOB: BLOB,
BOOLEAN: BOOLEAN, BOOLEAN: BOOLEAN,
ENUM: ENUM,
STRING: STRING, STRING: STRING,
UUID: UUID, UUID: UUID,
DATE: DATE, DATE: DATE,
...@@ -166,14 +180,18 @@ module.exports = { ...@@ -166,14 +180,18 @@ module.exports = {
INTEGER: INTEGER, INTEGER: INTEGER,
BIGINT: BIGINT, BIGINT: BIGINT,
REAL: REAL, REAL: REAL,
FLOAT: FLOAT FLOAT: FLOAT,
}; TEXT: TEXT
};
_.forIn(module.exports, function (DataType, key) { _.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key; if (!DataType.key) DataType.key = key;
if (!DataType.extend) { if (!DataType.extend) {
DataType.extend = function(oldType) { DataType.extend = function(oldType) {
return new DataType(oldType.options); 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').mssql;
var MssqlDialect = function(sequelize) { var MssqlDialect = function(sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var Utils = require('../../utils') var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, sequelizeErrors = require('../../errors.js'); , sequelizeErrors = require('../../errors.js')
, parserStore = require('../parserStore')('mssql');
var Query = function(connection, sequelize, options) { var Query = function(connection, sequelize, options) {
this.connection = connection; this.connection = connection;
...@@ -73,7 +74,14 @@ Query.prototype.run = function(sql, parameters) { ...@@ -73,7 +74,14 @@ Query.prototype.run = function(sql, parameters) {
request.on('row', function(columns) { request.on('row', function(columns) {
var row = {}; var row = {};
columns.forEach(function(column) { columns.forEach(function(column) {
row[column.metadata.colName] = column.value; var typeid = column.metadata.type.id
, value = column.value
, parse = parserStore.get(typeid);
if (value !== null & !!parse) {
value = parse(value);
}
row[column.metadata.colName] = value;
}); });
results.push(row); results.push(row);
......
...@@ -4,7 +4,9 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -4,7 +4,9 @@ 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
, parserMap = {};
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -16,10 +18,31 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -16,10 +18,31 @@ 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 (dataType) {
dataType.types.mysql.forEach(function (type) {
parserMap[type] = dataType.parse;
});
};
ConnectionManager.prototype.$clearTypeParser = function () {
parserMap = {};
};
ConnectionManager.$typecast = function (field, next) {
if (parserMap[field.type]) {
return parserMap[field.type](field);
}
return next();
};
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 +52,8 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -29,7 +52,8 @@ 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: ConnectionManager.$typecast
}; };
if (config.dialectOptions) { if (config.dialectOptions) {
......
'use strict'; 'use strict';
var BaseTypes = require('../../data-types') var wkx = require('wkx')
, 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 = ['LONGLONG'];
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 = ['NEWDECIMAL'];
BaseTypes.UUID.types.mysql = false;
BaseTypes.ENUM.types.mysql = false;
BaseTypes.REAL.types.mysql = ['DOUBLE'];
BaseTypes.DOUBLE.types.mysql = ['DOUBLE'];
var DATE = BaseTypes.DATE.inherits();
DATE.prototype.$stringify = function (date, options) {
date = BaseTypes.DATE.prototype.$applyTimezone(date, options);
return date.format('YYYY-MM-DD HH:mm:ss');
};
var UUID = BaseTypes.UUID.inherits();
UUID.prototype.toSql = function() { UUID.prototype.toSql = function() {
return 'CHAR(36) BINARY'; return 'CHAR(36) BINARY';
}; };
var SUPPORTED_GEOMETRY_TYPES = ['POINT', 'LINESTRING', 'POLYGON']; var SUPPORTED_GEOMETRY_TYPES = ['POINT', 'LINESTRING', 'POLYGON'];
var GEOMETRY = function() { var GEOMETRY = BaseTypes.GEOMETRY.inherits(function() {
if (!(this instanceof GEOMETRY)) return new GEOMETRY(); if (!(this instanceof GEOMETRY)) return new GEOMETRY();
BaseTypes.GEOMETRY.apply(this, arguments); BaseTypes.GEOMETRY.apply(this, arguments);
...@@ -28,23 +49,44 @@ var GEOMETRY = function() { ...@@ -28,23 +49,44 @@ var GEOMETRY = function() {
} else { } else {
throw new Error('Supported geometry types are: ' + SUPPORTED_GEOMETRY_TYPES.join(', ')); throw new Error('Supported geometry types are: ' + SUPPORTED_GEOMETRY_TYPES.join(', '));
} }
}; });
util.inherits(GEOMETRY, BaseTypes.GEOMETRY);
GEOMETRY.parse = GEOMETRY.prototype.parse = function(value) {
// For some reason, discard the first 4 bytes
value = value.buffer().slice(4);
return wkx.Geometry.parse(value).toGeoJSON();
};
GEOMETRY.prototype.toSql = function() { GEOMETRY.prototype.toSql = function() {
return this.sqlType; return this.sqlType;
}; };
var ENUM = BaseTypes.ENUM.inherits();
ENUM.prototype.toSql = function (options) {
return 'ENUM(' + _.map(this.values, function(value) {
return options.escape(value);
}).join(', ') + ')';
};
module.exports = { BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY'];
var exports = {
ENUM: ENUM,
DATE: DATE,
UUID: UUID, UUID: UUID,
GEOMETRY: GEOMETRY GEOMETRY: GEOMETRY
}; };
_.forIn(module.exports, function (DataType, key) { _.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key; if (!DataType.key) DataType.key = key;
if (!DataType.extend) { if (!DataType.extend) {
DataType.extend = function(oldType) { DataType.extend = function(oldType) {
return new DataType(oldType.options); 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;
......
'use strict'; 'use strict';
var Utils = require('../../utils') var Utils = require('../../utils');
, DataTypes = require('../../data-types')
, SqlString = require('../../sql-string')
, Wkt = require('wellknown');
var QueryGenerator = { var QueryGenerator = {
dialect: 'mysql', dialect: 'mysql',
...@@ -220,16 +217,7 @@ var QueryGenerator = { ...@@ -220,16 +217,7 @@ var QueryGenerator = {
}; };
} }
var template; var template = attribute.type.toString({ escape: this.escape.bind(this) });
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
template = 'ENUM(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + ')';
} else {
template = attribute.type.toString();
}
if (attribute.allowNull === false) { if (attribute.allowNull === false) {
template += ' NOT NULL'; template += ' NOT NULL';
...@@ -312,23 +300,6 @@ var QueryGenerator = { ...@@ -312,23 +300,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.
* *
...@@ -350,10 +321,6 @@ var QueryGenerator = { ...@@ -350,10 +321,6 @@ var QueryGenerator = {
*/ */
dropForeignKeyQuery: function(tableName, foreignKey) { dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';'; return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';';
},
geometrySelect: function(column) {
return 'AsText(' + this.quoteIdentifiers(column) + ') AS ' + column;
} }
}; };
......
'use strict';
var stores = {};
module.exports = function (dialect) {
stores[dialect] = stores[dialect] || {};
return {
clear: function () {
stores[dialect] = {};
},
refresh: function (dataType) {
dataType.types[dialect].forEach(function (type) {
stores[dialect][type] = dataType.parse;
});
},
get: function (type) {
return stores[dialect][type];
}
};
};
...@@ -5,7 +5,8 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -5,7 +5,8 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise') , Promise = require('../../promise')
, sequelizeErrors = require('../../errors') , sequelizeErrors = require('../../errors')
, semver = require('semver'); , semver = require('semver')
, dataTypes = require('../../data-types');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -17,10 +18,35 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -17,10 +18,35 @@ 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.postgres);
}; };
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 (dataType) {
var self = this;
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 = {};
...@@ -103,12 +129,29 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -103,12 +129,29 @@ ConnectionManager.prototype.connect = function(config) {
} }
if (!self.sequelize.config.keepDefaultTimezone) { if (!self.sequelize.config.keepDefaultTimezone) {
query += 'SET client_min_messages TO warning; SET TIME ZONE INTERVAL \'' + self.sequelize.options.timezone + '\' HOUR TO MINUTE'; query += 'SET client_min_messages TO warning; SET TIME ZONE INTERVAL \'' + self.sequelize.options.timezone + '\' HOUR TO MINUTE;';
}
// oids for hstore and geometry are dynamic - so select them at connection time
if (dataTypes.HSTORE.types.postgres.oids.length === 0) {
query += 'SELECT typname, oid, typarray FROM pg_type WHERE typtype = \'b\' AND typname IN (\'hstore\', \'geometry\')';
} }
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
connection.query(query).on('error', function (err) { connection.query(query).on('error', function (err) {
reject(err); reject(err);
}).on('row', function (row) {
var type;
if (row.typname === 'geometry') {
type = dataTypes.postgres.GEOMETRY;
} else if (row.typname === 'hstore') {
type = dataTypes.postgres.HSTORE;
}
type.types.postgres.oids.push(row.oid);
type.types.postgres.array_oids.push(row.typarray);
self.$refreshTypeParser(type);
}).on('end', function () { }).on('end', function () {
resolve(); resolve();
}); });
......
...@@ -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,
......
...@@ -2,11 +2,8 @@ ...@@ -2,11 +2,8 @@
/* jshint -W110 */ /* jshint -W110 */
var Utils = require('../../utils') var Utils = require('../../utils')
, hstore = require('./hstore')
, range = require('./range')
, util = require('util') , util = require('util')
, DataTypes = require('../../data-types') , DataTypes = require('../../data-types')
, SqlString = require('../../sql-string')
, AbstractQueryGenerator = require('../abstract/query-generator') , AbstractQueryGenerator = require('../abstract/query-generator')
, primaryKeys = {} , primaryKeys = {}
, _ = require('lodash'); , _ = require('lodash');
...@@ -845,48 +842,6 @@ var QueryGenerator = { ...@@ -845,48 +842,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.
* *
...@@ -922,10 +877,6 @@ var QueryGenerator = { ...@@ -922,10 +877,6 @@ var QueryGenerator = {
} }
return AbstractQueryGenerator.setAutocommitQuery.call(this, value, options); return AbstractQueryGenerator.setAutocommitQuery.call(this, value, options);
},
geometrySelect: function(column) {
return this.quoteIdentifiers(column);
} }
}; };
......
...@@ -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
...@@ -85,8 +22,6 @@ var Query = function(client, sequelize, options) { ...@@ -85,8 +22,6 @@ var Query = function(client, sequelize, options) {
}; };
Utils.inherit(Query, AbstractQuery); Utils.inherit(Query, AbstractQuery);
Query.prototype.parseDialectSpecificFields = parseDialectSpecificFields;
/** /**
* rewrite query with parameters * rewrite query with parameters
*/ */
...@@ -156,22 +91,17 @@ Query.prototype.run = function(sql, parameters) { ...@@ -156,22 +91,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) {
results = rows.map(function(row) { return rows.map(function(row) {
return { return {
name: row.relname, name: row.relname,
tableName: row.relname.split('_')[0] tableName: row.relname.split('_')[0]
}; };
}); });
} else { } else if (isTableNameQuery) {
results = rows.map(function(row) { return Utils._.values(row); }); return rows.map(function(row) { return _.values(row); });
}
return results;
} }
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
...@@ -193,12 +123,11 @@ Query.prototype.run = function(sql, parameters) { ...@@ -193,12 +123,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 +175,9 @@ Query.prototype.run = function(sql, parameters) { ...@@ -246,9 +175,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 +187,6 @@ Query.prototype.run = function(sql, parameters) { ...@@ -258,25 +187,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 +230,6 @@ Query.prototype.run = function(sql, parameters) { ...@@ -320,24 +230,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 +237,11 @@ Query.prototype.run = function(sql, parameters) { ...@@ -345,27 +237,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 +294,19 @@ Query.prototype.formatError = function (err) { ...@@ -418,19 +294,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 +331,7 @@ Query.prototype.formatError = function (err) { ...@@ -455,7 +331,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,12 +4,15 @@ var AbstractConnectionManager = require('../abstract/connection-manager') ...@@ -4,12 +4,15 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise') , Promise = require('../../promise')
, sequelizeErrors = require('../../errors'); , dataTypes = require('../../data-types').sqlite
, sequelizeErrors = require('../../errors')
, parserStore = require('../parserStore')('sqlite');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
this.config = sequelize.config; this.config = sequelize.config;
this.dialect = dialect; this.dialect = dialect;
this.dialectName = this.sequelize.options.dialect;
this.connections = {}; this.connections = {};
// We attempt to parse file location from a connection uri but we shouldn't match sequelize default host. // We attempt to parse file location from a connection uri but we shouldn't match sequelize default host.
...@@ -20,10 +23,21 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -20,10 +23,21 @@ 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 = function (dataType) {
parserStore.refresh(dataType);
};
ConnectionManager.prototype.$clearTypeParser = function () {
parserStore.clear();
};
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) {
var warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'https://www.sqlite.org/datatype3.html');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://www.sqlite.org/datatype3.html';
BaseTypes.DATE.types.sqlite = ['DATETIME'];
var STRING = function() { BaseTypes.STRING.types.sqlite = ['VARCHAR', 'VARCHAR BINARY'];
if (!(this instanceof STRING)) return new STRING(); BaseTypes.CHAR.types.sqlite = ['CHAR', 'CHAR BINARY'];
BaseTypes.STRING.apply(this, arguments); BaseTypes.TEXT.types.sqlite = ['TEXT'];
}; BaseTypes.INTEGER.types.sqlite = ['INTEGER'];
util.inherits(STRING, BaseTypes.STRING); 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 = false;
BaseTypes.REAL.types.sqlite = ['REAL'];
BaseTypes.DOUBLE.types.sqlite = ['DOUBLE PRECISION'];
BaseTypes.GEOMETRY.types.sqlite = false;
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
}
};
STRING.prototype.toSql = function() { var STRING = BaseTypes.STRING.inherits();
STRING.prototype.toSql = function() {
if (this._binary) { if (this._binary) {
return 'VARCHAR BINARY(' + this._length + ')'; return 'VARCHAR BINARY(' + this._length + ')';
} else { } else {
return BaseTypes.STRING.prototype.toSql.call(this); return BaseTypes.STRING.prototype.toSql.call(this);
} }
}; };
BaseTypes.TEXT.prototype.toSql = function() { var TEXT = BaseTypes.TEXT.inherits();
TEXT.prototype.toSql = function() {
if (this._length) { if (this._length) {
this.warn('SQLite does not support TEXT with options. Plain `TEXT` will be used instead.'); warn('SQLite does not support TEXT with options. Plain `TEXT` will be used instead.');
this._length = undefined; this._length = undefined;
} }
return 'TEXT'; return 'TEXT';
}; };
var CHAR = function() {
if (!(this instanceof CHAR)) return new CHAR();
BaseTypes.CHAR.apply(this, arguments);
};
util.inherits(CHAR, BaseTypes.CHAR);
CHAR.prototype.toSql = function() { var CHAR = BaseTypes.CHAR.inherits();
CHAR.prototype.toSql = function() {
if (this._binary) { if (this._binary) {
return 'CHAR BINARY(' + this._length + ')'; return 'CHAR BINARY(' + this._length + ')';
} else { } else {
return BaseTypes.CHAR.prototype.toSql.call(this); return BaseTypes.CHAR.prototype.toSql.call(this);
} }
}; };
var NUMBER = function() {
BaseTypes.NUMBER.apply(this, arguments);
};
util.inherits(NUMBER, BaseTypes.NUMBER);
NUMBER.prototype.toSql = function() { var NUMBER = BaseTypes.NUMBER.inherits();
NUMBER.prototype.toSql = function() {
var result = this.key; var result = this.key;
if (this._unsigned) { if (this._unsigned) {
...@@ -65,77 +79,79 @@ NUMBER.prototype.toSql = function() { ...@@ -65,77 +79,79 @@ NUMBER.prototype.toSql = function() {
result += ')'; result += ')';
} }
return result; return result;
}; };
var INTEGER = 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); if (!(this instanceof INTEGER)) return new INTEGER(options);
NUMBER.call(this, options); BaseTypes.INTEGER.call(this, options);
}; });
util.inherits(INTEGER, BaseTypes.INTEGER); INTEGER.prototype.key = INTEGER.key = 'INTEGER';
INTEGER.prototype.key = INTEGER.key = 'INTEGER'; INTEGER.prototype.toSql = function() {
INTEGER.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this); return NUMBER.prototype.toSql.call(this);
}; };
var BIGINT = function(length) { var BIGINT = BaseTypes.BIGINT.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); BaseTypes.BIGINT.call(this, options);
}; });
util.inherits(BIGINT, BaseTypes.BIGINT); BIGINT.prototype.key = BIGINT.key = 'BIGINT';
BIGINT.prototype.key = BIGINT.key = 'BIGINT'; BIGINT.prototype.toSql = function() {
BIGINT.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this); return NUMBER.prototype.toSql.call(this);
}; };
var FLOAT = 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); if (!(this instanceof FLOAT)) return new FLOAT(options);
NUMBER.call(this, options); BaseTypes.FLOAT.call(this, options);
}; });
util.inherits(FLOAT, BaseTypes.FLOAT); FLOAT.prototype.key = FLOAT.key = 'FLOAT';
FLOAT.prototype.key = FLOAT.key = 'FLOAT'; FLOAT.prototype.toSql = function() {
FLOAT.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this); return NUMBER.prototype.toSql.call(this);
}; };
var DOUBLE = 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); if (!(this instanceof DOUBLE)) return new DOUBLE(options);
NUMBER.call(this, options); BaseTypes.DOUBLE.call(this, options);
}; });
util.inherits(DOUBLE, BaseTypes.DOUBLE); DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION';
DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION'; DOUBLE.prototype.toSql = function() {
DOUBLE.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this); return NUMBER.prototype.toSql.call(this);
}; };
var REAL = function(length, decimals) { var REAL = BaseTypes.REAL.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); BaseTypes.REAL.call(this, options);
}; });
util.inherits(REAL, BaseTypes.REAL); REAL.prototype.key = REAL.key = 'REAL';
REAL.prototype.key = REAL.key = 'REAL'; REAL.prototype.toSql = function() {
REAL.prototype.toSql = function() {
return NUMBER.prototype.toSql.call(this); return NUMBER.prototype.toSql.call(this);
}; };
var ENUM = BaseTypes.ENUM.inherits();
ENUM.prototype.toSql = function () {
return 'TEXT';
};
module.exports = { var exports = {
DATE: DATE,
STRING: STRING, STRING: STRING,
CHAR: CHAR, CHAR: CHAR,
NUMBER: NUMBER, NUMBER: NUMBER,
...@@ -143,14 +159,20 @@ module.exports = { ...@@ -143,14 +159,20 @@ module.exports = {
REAL: REAL, REAL: REAL,
'DOUBLE PRECISION': DOUBLE, 'DOUBLE PRECISION': DOUBLE,
INTEGER: INTEGER, INTEGER: INTEGER,
BIGINT: BIGINT BIGINT: BIGINT,
}; TEXT: TEXT,
ENUM: ENUM
};
_.forIn(module.exports, function (DataType, key) { _.forIn(exports, function (DataType, key) {
if (!DataType.key) DataType.key = key; if (!DataType.key) DataType.key = key;
if (!DataType.extend) { if (!DataType.extend) {
DataType.extend = function(oldType) { DataType.extend = function(oldType) {
return new DataType(oldType.options); return new DataType(oldType.options);
}; };
} }
}); });
\ No newline at end of file
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;
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
/* jshint -W110 */ /* jshint -W110 */
var Utils = require('../../utils') var Utils = require('../../utils')
, DataTypes = require('../../data-types')
, Transaction = require('../../transaction') , Transaction = require('../../transaction')
, _ = require('lodash'); , _ = require('lodash');
...@@ -195,15 +194,7 @@ var QueryGenerator = { ...@@ -195,15 +194,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) {
replacements.type = 'TEXT';
if (!(Array.isArray(dataType.values) && (dataType.values.length > 0))) {
throw new Error("Values for ENUM haven't been defined.");
}
}
if (dataType.hasOwnProperty('allowNull') && !dataType.allowNull) { if (dataType.hasOwnProperty('allowNull') && !dataType.allowNull) {
template += ' NOT NULL'; template += ' NOT NULL';
......
'use strict'; 'use strict';
var Utils = require('../../utils') var Utils = require('../../utils')
, _ = require('lodash')
, Promise = require('../../promise')
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
, sequelizeErrors = require('../../errors.js'); , sequelizeErrors = require('../../errors.js')
, parserStore = require('../parserStore')('sqlite');
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 +50,28 @@ Query.formatBindParameters = function(sql, values, dialect) { ...@@ -47,6 +50,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;
...@@ -61,24 +86,21 @@ Query.prototype.run = function(sql, parameters) { ...@@ -61,24 +86,21 @@ Query.prototype.run = function(sql, parameters) {
this.sequelize.log('Executing (' + (this.database.uuid || 'default') + '): ' + this.sql, this.options); this.sequelize.log('Executing (' + (this.database.uuid || 'default') + '): ' + this.sql, this.options);
promise = new Utils.Promise(function(resolve) { promise = new Promise(function(resolve) {
var columnTypes = {}; var columnTypes = {};
self.database.serialize(function() { self.database.serialize(function() {
var executeSql = function() { var executeSql = function() {
if (self.sql.indexOf('-- ') === 0) { if (self.sql.indexOf('-- ') === 0) {
return resolve(); return resolve();
} else { } else {
resolve(new Utils.Promise(function(resolve, reject) { resolve(new Promise(function(resolve, reject) {
var afterExecute = function(err, results) { var afterExecute = function(err, results) {
if (err) { if (err) {
err.sql = self.sql; err.sql = self.sql;
reject(self.formatError(err)); reject(self.formatError(err));
} else { } else {
var metaData = this; var metaData = this
metaData.columnTypes = columnTypes; , result = self.instance;
var result = self.instance;
// add the inserted row id to the instance // add the inserted row id to the instance
if (self.isInsertQuery(results, metaData)) { if (self.isInsertQuery(results, metaData)) {
...@@ -93,29 +115,51 @@ Query.prototype.run = function(sql, parameters) { ...@@ -93,29 +115,51 @@ 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
result[name] = new Date(val + self.sequelize.options.timezone);
} else { } else {
result[name] = new Date(val); // We already have a timezone stored in the string model = self.options.model;
} }
var tableName = model.getTableName().toString().replace(/`/g, '')
, tableTypes = columnTypes[tableName];
if (tableTypes && !(name in tableTypes)) {
// The column is aliased
_.forOwn(model.rawAttributes, function (attribute, key) {
if (name === key && attribute.field) {
name = attribute.field;
return false;
} }
} else if (metaData.columnTypes[name].lastIndexOf('BLOB') !== -1) { });
if (result[name]) {
result[name] = new Buffer(result[name]);
} }
var type = tableTypes[name];
if (type) {
if (type.indexOf('(') !== -1) {
// Remove the lenght part
type = type.substr(0, type.indexOf('('));
} }
type = type.replace('UNSIGNED', '').replace('ZEROFILL', '');
type = type.trim();
var parse = parserStore.get(type);
if (value !== null && parse) {
return parse(value, { timezone: self.sequelize.options.timezone});
} }
} }
return result; return value;
});
}); });
} }
...@@ -189,23 +233,28 @@ Query.prototype.run = function(sql, parameters) { ...@@ -189,23 +233,28 @@ Query.prototype.run = function(sql, parameters) {
tableNames.push(/FROM `(.*?)`/i.exec(self.sql)[1]); tableNames.push(/FROM `(.*?)`/i.exec(self.sql)[1]);
} }
// If we already have the metadata for the table, there's no need to ask for it again
tableNames = _.filter(tableNames, function (tableName) {
return !(tableName in columnTypes) && tableName !== 'sqlite_master';
});
if (!tableNames.length) { if (!tableNames.length) {
return executeSql(); return executeSql();
} else { } else {
return Utils.Promise.map(tableNames, function(tableName) { return Promise.map(tableNames, function(tableName) {
if (tableName !== 'sqlite_master') { return new Promise(function(resolve) {
return new Utils.Promise(function(resolve) { tableName = tableName.replace(/`/g, '');
// 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();
}); });
}); });
}
}).then(executeSql); }).then(executeSql);
} }
} else { } else {
...@@ -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;
} }
......
...@@ -368,10 +368,6 @@ Instance.prototype.set = function(key, value, options) { // testhint options:non ...@@ -368,10 +368,6 @@ Instance.prototype.set = function(key, value, options) { // testhint options:non
} }
} }
if (this.Model._hasGeometryAttributes && this.Model._isGeometryAttribute(key) && _.isString(value)) {
value = this.Model.attributes[key].type.parse(value);
}
if (!options.raw && ((!Utils.isPrimitive(value) && value !== null) || value !== originalValue)) { if (!options.raw && ((!Utils.isPrimitive(value) && value !== null) || value !== originalValue)) {
this._previousDataValues[key] = originalValue; this._previousDataValues[key] = originalValue;
this.changed(key, true); this.changed(key, true);
......
...@@ -93,15 +93,6 @@ var Model = function(name, attributes, options) { ...@@ -93,15 +93,6 @@ var Model = function(name, attributes, options) {
throw new Error('Unrecognized data type for field ' + name); throw new Error('Unrecognized data type for field ' + name);
} }
if (attribute.type instanceof DataTypes.ENUM) {
if (!attribute.values.length) {
throw new Error('Values for ENUM have not been defined.');
}
// BC compatible.
attribute.type.values = attribute.values;
}
return attribute; return attribute;
}, this); }, this);
}; };
...@@ -837,14 +828,6 @@ Model.prototype.refreshAttributes = function() { ...@@ -837,14 +828,6 @@ Model.prototype.refreshAttributes = function() {
} }
if (definition.hasOwnProperty('defaultValue')) { if (definition.hasOwnProperty('defaultValue')) {
if (typeof definition.defaultValue === 'function' && (
definition.defaultValue === DataTypes.NOW ||
definition.defaultValue === DataTypes.UUIDV1 ||
definition.defaultValue === DataTypes.UUIDV4
)) {
definition.defaultValue = new definition.defaultValue();
}
self._defaultValues[name] = Utils._.partial(Utils.toDefaultValue, definition.defaultValue); self._defaultValues[name] = Utils._.partial(Utils.toDefaultValue, definition.defaultValue);
} }
......
...@@ -82,26 +82,7 @@ QueryInterface.prototype.createTable = function(tableName, attributes, options, ...@@ -82,26 +82,7 @@ QueryInterface.prototype.createTable = function(tableName, attributes, options,
attribute = { type: attribute, allowNull: true }; attribute = { type: attribute, allowNull: true };
} }
attribute.type = self.sequelize.normalizeDataType(attribute.type); attribute = self.sequelize.normalizeAttribute(attribute);
if (attribute.hasOwnProperty('defaultValue')) {
if (typeof attribute.defaultValue === 'function' && (
attribute.defaultValue === DataTypes.NOW ||
attribute.defaultValue === DataTypes.UUIDV1 ||
attribute.defaultValue === DataTypes.UUIDV4
)) {
attribute.defaultValue = new attribute.defaultValue();
}
}
if (attribute.type instanceof DataTypes.ENUM) {
// The ENUM is a special case where the type is an object containing the values
attribute.values = attribute.values || attribute.type.values || [];
if (!attribute.values.length) {
throw new Error('Values for ENUM haven\'t been defined.');
}
}
return attribute; return attribute;
}); });
......
...@@ -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.
* *
...@@ -1304,9 +1308,27 @@ Sequelize.prototype.normalizeAttribute = function(attribute) { ...@@ -1304,9 +1308,27 @@ Sequelize.prototype.normalizeAttribute = function(attribute) {
attribute.type = this.normalizeDataType(attribute.type); attribute.type = this.normalizeDataType(attribute.type);
if (attribute.hasOwnProperty('defaultValue')) {
if (typeof attribute.defaultValue === 'function' && (
attribute.defaultValue === DataTypes.NOW ||
attribute.defaultValue === DataTypes.UUIDV1 ||
attribute.defaultValue === DataTypes.UUIDV4
)) {
attribute.defaultValue = new attribute.defaultValue();
}
}
if (attribute.type instanceof DataTypes.ENUM) { if (attribute.type instanceof DataTypes.ENUM) {
// The ENUM is a special case where the type is an object containing the values // The ENUM is a special case where the type is an object containing the values
attribute.values = attribute.values || attribute.type.values || []; if (attribute.values) {
attribute.type.values = attribute.type.options.values = attribute.values;
} else {
attribute.values = attribute.type.values;
}
if (!attribute.values.length) {
throw new Error('Values for ENUM have not been defined.');
}
} }
return attribute; return attribute;
......
'use strict'; 'use strict';
/* jshint -W110 */ /* jshint -W110 */
var moment = require('moment-timezone') var dataTypes = require('./data-types')
, isArrayBufferView , _ = require('lodash')
, 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 +12,7 @@ SqlString.escapeId = function(val, forbidQualified) { ...@@ -30,19 +12,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,21 +30,23 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -60,21 +30,23 @@ 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, { timezone: timeZone });
} }
if (Buffer.isBuffer(val)) { if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val, dialect); if (dataTypes[dialect].BLOB) {
return dataTypes[dialect].BLOB.prototype.stringify(val);
} }
if (Array.isArray(val) || isArrayBufferView(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field); return dataTypes.BLOB.prototype.stringify(val);
} }
if (typeof val === 'object' && val !== null) { if (Array.isArray(val)) {
if (stringifyObjects) { var escape = _.partialRight(SqlString.escape, timeZone, dialect);
val = val.toString(); if (dialect === 'postgres') {
return dataTypes.ARRAY.prototype.stringify(val, {escape: escape});
} else { } else {
return SqlString.objectToValues(val, timeZone); return '[' + val.map(escape) + ']';
} }
} }
...@@ -98,43 +70,6 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -98,43 +70,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 +78,7 @@ SqlString.format = function(sql, values, timeZone, dialect) { ...@@ -143,7 +78,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 +89,9 @@ SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) { ...@@ -154,51 +89,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')
, sinon = require('sinon')
, _ = require('lodash')
, moment = require('moment')
, current = Support.sequelize
, uuid = require('node-uuid')
, DataTypes = require('../../lib/data-types')
, dialect = Support.getTestDialect();
describe(Support.getTestDialectTeaser('DataTypes'), function() {
afterEach(function () {
// Restore some sanity by resetting all parsers
switch (dialect) {
case 'postgres':
var types = require('../../node_modules/pg/node_modules/pg-types');
_.each(DataTypes, function (dataType) {
if (dataType.types && dataType.types.postgres) {
dataType.types.postgres.oids.forEach(function (oid) {
types.setTypeParser(oid, _.identity);
});
}
});
require('../../node_modules/pg/node_modules/pg-types/lib/binaryParsers').init(function (oid, converter) {
types.setTypeParser(oid, 'binary', converter);
});
require('../../node_modules/pg/node_modules/pg-types/lib/textParsers').init(function (oid, converter) {
types.setTypeParser(oid, 'text', converter);
});
break;
default:
this.sequelize.connectionManager.$clearTypeParser();
}
this.sequelize.connectionManager.refreshTypeParser(DataTypes[dialect]); // Reload custom parsers
});
it('allows me to return values from a custom parse function', function () {
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');
});
current.refreshTypes();
var User = current.define('user', {
dateField: Sequelize.DATE
}, {
timestamps: false
});
return current.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;
delete Sequelize.DATE.parse;
});
});
var testSuccess = function (Type, value) {
var parse = Type.constructor.parse = sinon.spy(function (value) {
return value;
});
var stringify = Type.constructor.prototype.stringify = sinon.spy(function (value) {
return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments);
});
current.refreshTypes();
var User = current.define('user', {
field: Type
}, {
timestamps: false
});
return current.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;
delete Type.constructor.parse;
delete Type.constructor.prototype.stringify;
});
};
var testFailure = function (Type, value) {
Type.constructor.parse = _.noop();
expect(function () {
current.refreshTypes();
}).to.throw('Parse function not supported for type ' + Type.key + ' in dialect ' + dialect);
delete Type.constructor.parse;
};
if (dialect === 'postgres') {
it('calls parse and stringify for JSON', function () {
var Type = new Sequelize.JSON();
return testSuccess(Type, { test: 42, nested: { foo: 'bar' }});
});
it('calls parse and stringify for JSONB', function () {
var Type = new Sequelize.JSONB();
return testSuccess(Type, { test: 42, nested: { foo: 'bar' }});
});
it('calls parse and stringify for HSTORE', function () {
var Type = new Sequelize.HSTORE();
return testSuccess(Type, { test: 42, nested: false });
});
it('calls parse and stringify for RANGE', function () {
var Type = new Sequelize.RANGE(new Sequelize.INTEGER());
return testSuccess(Type, [1, 2]);
});
}
it('calls parse and stringify for DATE', function () {
var Type = new Sequelize.DATE();
return testSuccess(Type, new Date());
});
it('calls parse and stringify for DATEONLY', function () {
var Type = new Sequelize.DATEONLY();
return testSuccess(Type, new Date());
});
it('calls parse and stringify for TIME', function () {
var Type = new Sequelize.TIME();
return testSuccess(Type, new Date());
});
it('calls parse and stringify for BLOB', function () {
var Type = new Sequelize.BLOB();
return testSuccess(Type, 'foobar');
});
it('calls parse and stringify for CHAR', function () {
var Type = new Sequelize.CHAR();
return testSuccess(Type, 'foobar');
});
it('calls parse and stringify for STRING', function () {
var Type = new Sequelize.STRING();
return testSuccess(Type, 'foobar');
});
it('calls parse and stringify for TEXT', function () {
var Type = new Sequelize.TEXT();
if (dialect === 'mssql') {
// Text uses nvarchar, same type as string
testFailure(Type);
} else {
return testSuccess(Type, 'foobar');
}
});
it('calls parse and stringify for BOOLEAN', function () {
var Type = new Sequelize.BOOLEAN();
return testSuccess(Type, true);
});
it('calls parse and stringify for INTEGER', function () {
var Type = new Sequelize.INTEGER();
return testSuccess(Type, 1);
});
it('calls parse and stringify for DECIMAL', function () {
var Type = new Sequelize.DECIMAL();
return testSuccess(Type, 1.5);
});
it('calls parse and stringify for BIGINT', function () {
var Type = new Sequelize.BIGINT();
if (dialect === 'mssql') {
// Same type as integer
testFailure(Type);
} else {
return testSuccess(Type, 1);
}
});
it('calls parse and stringify for DOUBLE', function () {
var Type = new Sequelize.DOUBLE();
return testSuccess(Type, 1.5);
});
it('calls parse and stringify for FLOAT', function () {
var Type = new Sequelize.FLOAT();
if (dialect === 'postgres') {
// Postgres doesn't have float, maps to either decimal or double
testFailure(Type);
} else {
return testSuccess(Type, 1.5);
}
});
it('calls parse and stringify for REAL', function () {
var Type = new Sequelize.REAL();
return testSuccess(Type, 1.5);
});
it('calls parse and stringify for GEOMETRY', function () {
var Type = new Sequelize.GEOMETRY();
if (['postgres', 'mysql', 'mariadb'].indexOf(dialect) !== -1) {
return testSuccess(Type, { type: "Point", coordinates: [125.6, 10.1] });
} else {
// Not implemented yet
testFailure(Type);
}
});
it('calls parse and stringify for UUID', function () {
var Type = new Sequelize.UUID();
if (['postgres', 'sqlite'].indexOf(dialect) !== -1) {
return testSuccess(Type, uuid.v4());
} else {
// No native uuid type
testFailure(Type);
}
});
it('calls parse and stringify for ENUM', function () {
var Type = new Sequelize.ENUM('hat', 'cat');
// No dialects actually allow us to identify that we get an enum back..
testFailure(Type);
});
});
...@@ -566,7 +566,7 @@ if (Support.dialectIsMySQL()) { ...@@ -566,7 +566,7 @@ if (Support.dialectIsMySQL()) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize); if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize); if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize);
} }
QueryGenerator.options = context.options; QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect; QueryGenerator._dialect = this.sequelize.dialect;
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments); var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation); expect(conditions).to.deep.equal(test.expectation);
......
...@@ -658,6 +658,7 @@ if (dialect.match(/^postgres/)) { ...@@ -658,6 +658,7 @@ if (dialect.match(/^postgres/)) {
var period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; var period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
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
......
...@@ -20,6 +20,7 @@ if (dialect.match(/^postgres/)) { ...@@ -20,6 +20,7 @@ if (dialect.match(/^postgres/)) {
numbers: { type: DataTypes.ARRAY(DataTypes.FLOAT) }, numbers: { type: DataTypes.ARRAY(DataTypes.FLOAT) },
document: { type: DataTypes.HSTORE, defaultValue: { default: '"value"' } } document: { type: DataTypes.HSTORE, defaultValue: { default: '"value"' } }
}); });
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
...@@ -548,7 +549,7 @@ if (dialect.match(/^postgres/)) { ...@@ -548,7 +549,7 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {data: new Buffer('Sequelize') }], arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65');" expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65');"
}, { }, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1, 2, 3])}], arguments: ['myTable', {name: 'foo', numbers: [1, 2, 3]}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]);" expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]);"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
...@@ -601,7 +602,7 @@ if (dialect.match(/^postgres/)) { ...@@ -601,7 +602,7 @@ if (dialect.match(/^postgres/)) {
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00');", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00');",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1, 2, 3])}], arguments: ['myTable', {name: 'foo', numbers: [1, 2, 3]}],
expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]);", expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]);",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
...@@ -744,7 +745,7 @@ if (dialect.match(/^postgres/)) { ...@@ -744,7 +745,7 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {bar: 2}, {name: 'foo'}, { returning: true }], arguments: ['myTable', {bar: 2}, {name: 'foo'}, { returning: true }],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\" = 'foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\" = 'foo' RETURNING *"
}, { }, {
arguments: ['myTable', {numbers: new Uint8Array([1, 2, 3])}, {name: 'foo'}], arguments: ['myTable', {numbers: [1, 2, 3]}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\" = 'foo'" expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\" = 'foo'"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
...@@ -802,7 +803,7 @@ if (dialect.match(/^postgres/)) { ...@@ -802,7 +803,7 @@ if (dialect.match(/^postgres/)) {
expectation: "UPDATE myTable SET bar=2 WHERE name = 'foo'", expectation: "UPDATE myTable SET bar=2 WHERE name = 'foo'",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {numbers: new Uint8Array([1, 2, 3])}, {name: 'foo'}], arguments: ['myTable', {numbers: [1, 2, 3]}, {name: 'foo'}],
expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name = 'foo'", expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name = 'foo'",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
...@@ -942,11 +943,12 @@ if (dialect.match(/^postgres/)) { ...@@ -942,11 +943,12 @@ if (dialect.match(/^postgres/)) {
it(title, function() { it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
if (test.needsSequelize) { if (test.needsSequelize) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize); if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize); if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize);
} }
QueryGenerator.options = context.options; QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect; QueryGenerator._dialect = this.sequelize.dialect;
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments); var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation); expect(conditions).to.deep.equal(test.expectation);
......
...@@ -57,8 +57,9 @@ if (dialect.match(/^postgres/)) { ...@@ -57,8 +57,9 @@ if (dialect.match(/^postgres/)) {
}); });
it('should handle date values', function () { it('should handle date values', function () {
expect(range.stringify([new Date(Date.UTC(2000, 1, 1)), var Range = new DataTypes.postgres.RANGE(DataTypes.DATE);
new Date(Date.UTC(2000, 1, 2))])).to.equal('("2000-02-01T00:00:00.000Z","2000-02-02T00:00:00.000Z")'); expect(Range.stringify([new Date(Date.UTC(2000, 1, 1)),
new Date(Date.UTC(2000, 1, 2))], { timezone: '+02:00' })).to.equal('\'("2000-02-01 02:00:00.000 +02:00","2000-02-02 02:00:00.000 +02:00")\'');
}); });
}); });
...@@ -80,8 +81,12 @@ if (dialect.match(/^postgres/)) { ...@@ -80,8 +81,12 @@ if (dialect.match(/^postgres/)) {
var testRange = [5,10]; var testRange = [5,10];
testRange.inclusive = [true, true]; testRange.inclusive = [true, true];
expect(range.parse(range.stringify(testRange), DataTypes.RANGE(DataTypes.INTEGER))).to.deep.equal(testRange); var Range = new DataTypes.postgres.RANGE(DataTypes.INTEGER);
expect(range.parse(range.stringify(range.parse(range.stringify(testRange), DataTypes.RANGE(DataTypes.INTEGER))), DataTypes.RANGE(DataTypes.INTEGER))).to.deep.equal(testRange);
var stringified = Range.stringify(testRange, {});
stringified = stringified.substr(1, stringified.length - 2); // Remove the escaping ticks
expect(DataTypes.postgres.RANGE.parse(stringified, 3904, function () { return DataTypes.postgres.INTEGER.parse; })).to.deep.equal(testRange);
}); });
}); });
}); });
......
...@@ -10,9 +10,21 @@ if (dialect === 'sqlite') { ...@@ -10,9 +10,21 @@ if (dialect === 'sqlite') {
describe('[SQLITE Specific] DAO', function() { describe('[SQLITE Specific] DAO', function() {
beforeEach(function() { beforeEach(function() {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: DataTypes.STRING username: DataTypes.STRING,
dateField: {
type: DataTypes.DATE,
field: 'date_field'
}
}); });
return this.User.sync({ force: true }); this.Project = this.sequelize.define('project', {
dateField: {
type: DataTypes.DATE,
field: 'date_field'
}
});
this.User.hasMany(this.Project);
return this.sequelize.sync({ force: true });
}); });
describe('findAll', function() { describe('findAll', function() {
...@@ -25,13 +37,39 @@ if (dialect === 'sqlite') { ...@@ -25,13 +37,39 @@ if (dialect === 'sqlite') {
return user.save().then(function() { return user.save().then(function() {
return self.User.create({ username: 'new user' }).then(function() { return self.User.create({ username: 'new user' }).then(function() {
return self.User.findAll({ return self.User.findAll({
where: ['createdAt > ?', new Date(2012, 1, 1)] where: { createdAt: { $gt: new Date(2012, 1, 1) }}
}).then(function(users) { }).then(function(users) {
expect(users).to.have.length(1); expect(users).to.have.length(1);
}); });
}); });
}); });
}); });
it('handles dates with aliasses correctly #3611', function() {
return this.User.create({
dateField: new Date(2010, 10, 10)
}).bind(this).then(function () {
return this.User.findAll().get(0);
}).then(function (user) {
expect(user.get('dateField')).to.be.an.instanceof(Date);
expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10));
});
});
it('handles dates in includes correctly #2644', function() {
return this.User.create({
projects: [
{ dateField: new Date(1990, 5, 5) }
]
}, { include: [this.Project]}).bind(this).then(function () {
return this.User.findAll({
include: [this.Project]
}).get(0);
}).then(function (user) {
expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date);
expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5));
});
});
}); });
describe('regression tests', function() { describe('regression tests', function() {
......
...@@ -529,7 +529,7 @@ if (dialect === 'sqlite') { ...@@ -529,7 +529,7 @@ if (dialect === 'sqlite') {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize); if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize); if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize);
} }
QueryGenerator.options = context.options; QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect; QueryGenerator._dialect = this.sequelize.dialect;
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments); var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation); expect(conditions).to.deep.equal(test.expectation);
......
...@@ -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);
} }
}); });
}); });
......
...@@ -23,6 +23,20 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -23,6 +23,20 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
}); });
it('works with aliases fields', function () {
var Pub = this.sequelize.define('Pub', {
location: {field: 'coordinates', type: DataTypes.GEOMETRY}
})
, point = {type: 'Point', coordinates: [39.807222, -76.984722]};
return Pub.sync({ force: true }).then(function () {
return Pub.create({location: point});
}).then(function (pub) {
expect(pub).not.to.be.null;
expect(pub.location).to.be.deep.eql(point);
});
});
it('should create a geometry object', function() { it('should create a geometry object', function() {
var User = this.User; var User = this.User;
var point = { type: 'Point', coordinates: [39.807222,-76.984722]}; var point = { type: 'Point', coordinates: [39.807222,-76.984722]};
......
...@@ -18,9 +18,9 @@ var chai = require('chai') ...@@ -18,9 +18,9 @@ var chai = require('chai')
var qq = function(str) { var qq = function(str) {
if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { if (dialect === 'postgres' || dialect === 'mssql') {
return '"' + str + '"'; return '"' + str + '"';
} else if (Support.dialectIsMySQL()) { } else if (Support.dialectIsMySQL() || dialect === 'sqlite') {
return '`' + str + '`'; return '`' + str + '`';
} else { } else {
return str; return str;
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!