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

Commit 7a8a1e70 by Jan Aagaard Meier

feat(datatypes) Finish work on datatypes refactor

1 parent 484c0c2d
......@@ -8,7 +8,6 @@ var util = require('util')
, Validator = require('validator')
, moment = require('moment-timezone')
, hstore = require('./dialects/postgres/hstore')
, wkx = require('wkx')
, deprecate = require('depd')('DataTypes');
/**
......@@ -47,8 +46,8 @@ var ABSTRACT = function(options) {
ABSTRACT.prototype.dialectTypes = '';
ABSTRACT.prototype.toString = function() {
return this.toSql();
ABSTRACT.prototype.toString = function(options) {
return this.toSql(options);
};
ABSTRACT.prototype.toSql = function() {
return this.key;
......@@ -464,11 +463,7 @@ DATEONLY.prototype.toSql = function() {
* @property HSTORE
*/
var HSTORE = function() {
if (!(this instanceof HSTORE)) return new HSTORE();
ABSTRACT.apply(this, arguments);
};
util.inherits(HSTORE, ABSTRACT);
var HSTORE = ABSTRACT.inherits();
HSTORE.prototype.key = HSTORE.key = 'HSTORE';
HSTORE.prototype.validate = function(value) {
......@@ -480,7 +475,7 @@ HSTORE.prototype.validate = function(value) {
};
HSTORE.prototype.escape = false;
HSTORE.prototype.stringify = function (value) {
HSTORE.prototype.$stringify = function (value) {
return '\'' + hstore.stringify(value) + '\'';
};
......@@ -499,7 +494,7 @@ JSONTYPE.prototype.validate = function(value) {
return true;
};
JSONTYPE.prototype.stringify = function (value, options) {
JSONTYPE.prototype.$stringify = function (value, options) {
return JSON.stringify(value);
};
......@@ -558,9 +553,17 @@ BLOB.prototype.validate = function(value) {
return true;
};
BLOB.prototype.escape = false;
BLOB.prototype.$stringify = function (value) {
if (!Buffer.isBuffer(value)) {
value = new Buffer(value);
}
var hex = value.toString('hex');
return this.$hexify(hex);
};
BLOB.prototype.$hexify = function (hex) {
return "X'" + hex + "'";
};
......@@ -571,7 +574,7 @@ BLOB.prototype.$stringify = function (value) {
* @property RANGE
*/
var RANGE = function (subtype) {
var RANGE = ABSTRACT.inherits(function (subtype) {
var options = _.isPlainObject(subtype) ? subtype : { subtype: subtype };
if (!options.subtype) options.subtype = new INTEGER();
......@@ -585,8 +588,7 @@ var RANGE = function (subtype) {
this._subtype = options.subtype.key;
this.options = options;
};
util.inherits(RANGE, ABSTRACT);
});
var pgRangeSubtypes = {
integer: 'int4range',
......@@ -738,6 +740,7 @@ var ENUM = ABSTRACT.inherits(function(value) {
};
if (!(this instanceof ENUM)) return new ENUM(options);
this.values = options.values;
this.options = options;
});
ENUM.prototype.key = ENUM.key = 'ENUM';
......@@ -800,15 +803,8 @@ var GEOMETRY = ABSTRACT.inherits(function(type, srid) {
GEOMETRY.prototype.key = GEOMETRY.key = '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.escape = false;
GEOMETRY.prototype.stringify = function (value) {
GEOMETRY.prototype.$stringify = function (value) {
return 'GeomFromText(\'' + Wkt.stringify(value) + '\')';
};
......
......@@ -19,6 +19,7 @@ ConnectionManager = function(dialect, sequelize) {
this.config = config;
this.dialect = dialect;
this.versionPromise = null;
this.dialectName = this.sequelize.options.dialect;
if (config.pool) {
config.pool = _.clone(config.pool); // Make sure we don't modify the existing config object (user might re-use it)
......@@ -44,6 +45,18 @@ ConnectionManager = function(dialect, sequelize) {
process.on('exit', this.onProcessExit);
};
ConnectionManager.prototype.refreshTypeParser = function(dataTypes) {
_.each(dataTypes, function (dataType, key) {
if (dataType.hasOwnProperty('parse')) {
if (dataType.types[this.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() {
var self = this;
......
......@@ -957,7 +957,7 @@ var QueryGenerator = {
if (field.type.stringify) {
// Users shouldn't have to worry about these args - just give them a function that takes a single arg
var simpleEscape = _.partialRight(SqlString.escape, this.options.timezone, this.dialect, field);
var simpleEscape = _.partialRight(SqlString.escape, this.options.timezone, this.dialect);
value = field.type.stringify(value, { escape: simpleEscape, field: field, timezone: this.options.timezone });
......@@ -970,7 +970,7 @@ var QueryGenerator = {
}
}
return SqlString.escape(value, this.options.timezone, this.dialect, field);
return SqlString.escape(value, this.options.timezone, this.dialect);
},
/**
......
......@@ -4,7 +4,8 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager
, Utils = require('../../utils')
, Promise = require('../../promise')
, sequelizeErrors = require('../../errors');
, sequelizeErrors = require('../../errors')
, parserStore = require('../parserStore')('mssql');
ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize);
......@@ -22,8 +23,12 @@ Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype)
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = function (dataTypes) {
require('../parsing').refresh(dataTypes, 'mssql');
ConnectionManager.prototype.$refreshTypeParser = function (dataType) {
parserStore.refresh(dataType);
};
ConnectionManager.prototype.$clearTypeParser = function () {
parserStore.clear();
};
ConnectionManager.prototype.connect = function(config) {
......
......@@ -8,20 +8,21 @@ module.exports = function (BaseTypes) {
BaseTypes.DATE.types.mssql = [42];
BaseTypes.STRING.types.mssql = [231, 173];
BaseTypes.CHAR.types.mssql = [175];
BaseTypes.TEXT.types.mssql = [];
BaseTypes.TEXT.types.mssql = false;
BaseTypes.INTEGER.types.mssql = [38];
BaseTypes.BIGINT.types.mssql = [127];
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 = [];
BaseTypes.ENUM.types.mssql = []; // Questionable .. overwritten by text
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];
// BaseTypes.GEOMETRY.types.mssql = [240]; // not yet supported
BaseTypes.GEOMETRY.types.mssql = false;
var BLOB = BaseTypes.BLOB.inherits();
......@@ -36,10 +37,7 @@ module.exports = function (BaseTypes) {
return 'VARBINARY(MAX)';
};
BLOB.prototype.escape = false;
BLOB.prototype.$stringify = function (value) {
var hex = value.toString('hex');
BLOB.prototype.$hexify = function (hex) {
return '0x' + hex;
};
......@@ -53,13 +51,12 @@ module.exports = function (BaseTypes) {
}
};
STRING.prototype.$stringify = function (value) {
STRING.prototype.escape = false;
STRING.prototype.$stringify = function (value, options) {
if (this._binary) {
this.escape = false;
return BLOB.prototype.$stringify(value);
} else {
this.escape = true;
return value;
return options.escape(value);
}
};
......
......@@ -3,7 +3,7 @@
var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query')
, sequelizeErrors = require('../../errors.js')
, parsers = require('../parsing').parsers.mssql;
, parserStore = require('../parserStore')('mssql');
var Query = function(connection, sequelize, options) {
this.connection = connection;
......@@ -74,16 +74,12 @@ Query.prototype.run = function(sql, parameters) {
request.on('row', function(columns) {
var row = {};
columns.forEach(function(column) {
if (['constraint_name', 'TABLE_SCHEMA', 'TABLE_NAME'].indexOf(column.metadata.colName) === -1) {
// console.log(column.metadata.colName)
// console.log(column.metadata.type)
}
var typeid = column.metadata.type.id
, value = column.value;
, value = column.value
, parse = parserStore.get(typeid);
if (typeid in parsers) {
value = parsers[typeid](value);
if (value !== null & parse) {
value = parse(value);
}
row[column.metadata.colName] = value;
});
......
......@@ -6,10 +6,8 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, Promise = require('../../promise')
, sequelizeErrors = require('../../errors')
, dataTypes = require('../../data-types').mysql
, _ = require('lodash')
, parserMap = {};
ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize);
......@@ -27,16 +25,24 @@ ConnectionManager = function(dialect, sequelize) {
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = function (dataTypes) {
_.each(dataTypes, function (dataType, key) {
if (dataType.parse && dataType.types.mysql) {
dataType.types.mysql.forEach(function (type) {
parserMap[type] = dataType.parse;
});
}
ConnectionManager.prototype.$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) {
var self = this;
return new Promise(function (resolve, reject) {
......@@ -47,13 +53,7 @@ ConnectionManager.prototype.connect = function(config) {
password: config.password,
database: config.database,
timezone: self.sequelize.options.timezone,
typeCast: function (field, next) {
if (parserMap[field.type]) {
return parserMap[field.type](field);
}
return next();
}
typeCast: ConnectionManager.$typecast
};
if (config.dialectOptions) {
......
'use strict';
var util = require('util')
var wkx = require('wkx')
, _ = require('lodash');
module.exports = function (BaseTypes) {
......@@ -11,15 +11,15 @@ module.exports = function (BaseTypes) {
BaseTypes.CHAR.types.mysql = ['STRING'];
BaseTypes.TEXT.types.mysql = ['BLOB'];
BaseTypes.INTEGER.types.mysql = ['LONG'];
BaseTypes.BIGINT.types.mysql = ['BIGINT'];
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 = ['DECIMAL'];
BaseTypes.UUID.types.mysql = ['UUID'];
BaseTypes.ENUM.types.mysql = ['TEXT']; // Questionable .. overwritten by text
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'];
......@@ -50,17 +50,30 @@ module.exports = function (BaseTypes) {
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() {
return this.sqlType;
};
BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY'];
var ENUM = BaseTypes.ENUM.inherits();
BaseTypes.DATE.types.mysql = ['DATETIME'];
ENUM.prototype.toSql = function (options) {
return 'ENUM(' + _.map(this.values, function(value) {
return options.escape(value);
}).join(', ') + ')';
};
BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY'];
var exports = {
ENUM: ENUM,
DATE: DATE,
UUID: UUID,
GEOMETRY: GEOMETRY
......
......@@ -220,16 +220,7 @@ var QueryGenerator = {
};
}
var template;
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();
}
var template = attribute.type.toString({ escape: this.escape.bind(this) });
if (attribute.allowNull === false) {
template += ' NOT NULL';
......
'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];
}
};
};
'use strict';
var _ = require('lodash');
module.exports.parsers = {
sqlite: {},
mssql: {}
};
module.exports.refresh = function (dataTypes, dialect) {
_.each(dataTypes, function (dataType, key) {
if (dataType.parse && dataType.types.sqlite) {
dataType.types[dialect].forEach(function (type) {
module.exports.parsers[dialect][type] = dataType.parse;
});
}
});
};
......@@ -5,12 +5,9 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, Utils = require('../../utils')
, Promise = require('../../promise')
, sequelizeErrors = require('../../errors')
<<<<<<< HEAD
, semver = require('semver');
=======
, semver = require('semver')
, dataTypes = require('../../data-types').postgres
, _ = require('lodash');
>>>>>>> feat(datatypes) Initial work on datatypes refactor
ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize);
......@@ -29,30 +26,26 @@ ConnectionManager = function(dialect, sequelize) {
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = function (dataTypes) {
ConnectionManager.prototype.$refreshTypeParser = function (dataType) {
var self = this;
_.each(dataTypes, function (dataType, key) {
if (dataType.parse && dataType.types.postgres) {
if (dataType.types.postgres.oids) {
dataType.types.postgres.oids.forEach(function (oid) {
self.lib.types.setTypeParser(oid, function (value) {
return dataType.parse(value, oid, self.lib.types.getTypeParser);
});
});
}
if (dataType.types.postgres.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();
});
});
}
}
});
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) {
......
......@@ -118,6 +118,10 @@ module.exports = function (BaseTypes) {
}
});
INTEGER.parse = function (value) {
return parseInt(value, 10);
};
// int4
BaseTypes.INTEGER.types.postgres = {
oids: [23],
......@@ -218,11 +222,9 @@ module.exports = function (BaseTypes) {
return 'BYTEA';
};
BLOB.prototype.$stringify = function (value) {
var hex = value.toString('hex');
BLOB.prototype.$hexify = function (hex) {
// bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex + "'";
return "E'\\\\x" + hex + "'";
};
BaseTypes.BLOB.types.postgres = {
......@@ -261,7 +263,7 @@ module.exports = function (BaseTypes) {
return 'ST_GeomFromGeoJSON(\'' + JSON.stringify(value) + '\')';
};
var HSTORE = BaseTypes.HSTORE;
var HSTORE = BaseTypes.HSTORE.inherits();
HSTORE.parse = function (value) {
return hstore.parse(value);
......@@ -272,7 +274,7 @@ module.exports = function (BaseTypes) {
array_oids: [2164513]
};
var RANGE = BaseTypes.RANGE;
var RANGE = BaseTypes.RANGE.inherits();
RANGE.oid_map = {
3904: 1007, // int4
......@@ -317,12 +319,10 @@ module.exports = function (BaseTypes) {
array_oids: [3905, 3907, 3909, 3911, 3913, 3927]
};
var ARRAY = BaseTypes.ARRAY;
BaseTypes.ARRAY.prototype.escape = false;
BaseTypes.ARRAY.prototype.$stringify = function (values, options) {
return 'ARRAY[' + values.map(function (value) {
if (this.type.stringify) {
var str = 'ARRAY[' + values.map(function (value) {
if (this.type && this.type.stringify) {
value = this.type.stringify(value, options);
if (this.type.escape === false) {
......@@ -330,7 +330,13 @@ module.exports = function (BaseTypes) {
}
}
return options.escape(value);
}, this).join(',') + ']::' + this.toSql();
}, this).join(',') + ']';
if (this.type) {
str += '::' + this.toSql();
}
return str;
};
var exports = {
......@@ -347,8 +353,7 @@ module.exports = function (BaseTypes) {
FLOAT: FLOAT,
GEOMETRY: GEOMETRY,
HSTORE: HSTORE,
RANGE: RANGE,
ARRAY: ARRAY
RANGE: RANGE
};
_.forIn(exports, function (DataType, key) {
......
......@@ -5,12 +5,14 @@ var AbstractConnectionManager = require('../abstract/connection-manager')
, Utils = require('../../utils')
, Promise = require('../../promise')
, dataTypes = require('../../data-types').sqlite
, sequelizeErrors = require('../../errors');
, sequelizeErrors = require('../../errors')
, parserStore = require('../parserStore')('sqlite');
ConnectionManager = function(dialect, sequelize) {
this.sequelize = sequelize;
this.config = sequelize.config;
this.dialect = dialect;
this.dialectName = this.sequelize.options.dialect;
this.connections = {};
// We attempt to parse file location from a connection uri but we shouldn't match sequelize default host.
......@@ -28,8 +30,12 @@ ConnectionManager = function(dialect, sequelize) {
Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
// Expose this as a method so that the parsing may be updated when the user has added additional, custom types
ConnectionManager.prototype.refreshTypeParser = function (dataTypes) {
require('../parsing').refresh(dataTypes, 'sqlite');
ConnectionManager.prototype.$refreshTypeParser = function (dataType) {
parserStore.refresh(dataType);
};
ConnectionManager.prototype.$clearTypeParser = function () {
parserStore.clear();
};
ConnectionManager.prototype.getConnection = function(options) {
......
......@@ -18,10 +18,10 @@ module.exports = function (BaseTypes) {
BaseTypes.BLOB.types.sqlite = ['TINYBLOB', 'BLOB', 'LONGBLOB'];
BaseTypes.DECIMAL.types.sqlite = ['DECIMAL'];
BaseTypes.UUID.types.sqlite = ['UUID'];
BaseTypes.ENUM.types.sqlite = ['TEXT']; // Questionable .. overwritten by text
BaseTypes.ENUM.types.sqlite = false;
BaseTypes.REAL.types.sqlite = ['REAL'];
BaseTypes.DOUBLE.types.sqlite = ['DOUBLE PRECISION'];
BaseTypes.GEOMETRY.types.mssql = [];
BaseTypes.GEOMETRY.types.sqlite = false;
var DATE = BaseTypes.DATE.inherits();
DATE.parse = function (date, options) {
......@@ -144,6 +144,12 @@ module.exports = function (BaseTypes) {
return NUMBER.prototype.toSql.call(this);
};
var ENUM = BaseTypes.ENUM.inherits();
ENUM.prototype.toSql = function () {
return 'TEXT';
};
var exports = {
DATE: DATE,
STRING: STRING,
......@@ -154,7 +160,8 @@ module.exports = function (BaseTypes) {
'DOUBLE PRECISION': DOUBLE,
INTEGER: INTEGER,
BIGINT: BIGINT,
TEXT: TEXT
TEXT: TEXT,
ENUM: ENUM
};
_.forIn(exports, function (DataType, key) {
......
......@@ -197,14 +197,6 @@ var QueryGenerator = {
var template = '<%= 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) {
template += ' NOT NULL';
}
......
......@@ -5,7 +5,7 @@ var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query')
, QueryTypes = require('../../query-types')
, sequelizeErrors = require('../../errors.js')
, parsers = require('../parsing').parsers.sqlite;
, parserStore = require('../parserStore')('sqlite');
var Query = function(database, sequelize, options) {
this.database = database;
......@@ -133,9 +133,10 @@ Query.prototype.run = function(sql, parameters) {
model = self.options.model;
}
var tableTypes = metaData.columnTypes[model.tableName];
var tableName = model.getTableName().toString().replace(/`/g, '')
, tableTypes = metaData.columnTypes[tableName];
if (!(name in tableTypes)) {
if (tableTypes && !(name in tableTypes)) {
// The column is aliased
_.forOwn(model.rawAttributes, function (attribute, key) {
if (name === key && attribute.field) {
......@@ -146,17 +147,18 @@ Query.prototype.run = function(sql, parameters) {
}
var type = tableTypes[name];
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 = parsers[type];
if (value !== null && parse) {
return parse(value, { timezone: self.sequelize.options.timezone});
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 value;
});
......@@ -229,8 +231,13 @@ Query.prototype.run = function(sql, parameters) {
var tableNames = [];
if (self.options && self.options.tableNames) {
tableNames = self.options.tableNames;
} else if (/FROM `(.*?)`/i.exec(self.sql)) {
tableNames.push(/FROM `(.*?)`/i.exec(self.sql)[1]);
} else {
if (/FROM `(.*?)`/i.exec(self.sql)) {
tableNames.push(/FROM `(.*?)`/i.exec(self.sql)[1]);
}
if (/FROM "(.*?)"/i.exec(self.sql)) {
tableNames.push(/FROM "(.*?)"/i.exec(self.sql)[1]);
}
}
if (!tableNames.length) {
......@@ -244,7 +251,9 @@ Query.prototype.run = function(sql, parameters) {
return Utils.Promise.map(tableNames, function(tableName) {
if (tableName !== 'sqlite_master') {
return new Utils.Promise(function(resolve) {
tableName = tableName.replace(/`/g, '');
columnTypes[tableName] = {};
self.database.all('PRAGMA table_info(`' + tableName + '`)', function(err, results) {
if (!err) {
results.forEach(function (result) {
......
......@@ -93,15 +93,6 @@ var Model = function(name, attributes, options) {
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;
}, this);
};
......@@ -837,14 +828,6 @@ Model.prototype.refreshAttributes = function() {
}
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);
}
......
......@@ -82,26 +82,7 @@ QueryInterface.prototype.createTable = function(tableName, attributes, options,
attribute = { type: attribute, allowNull: true };
}
attribute.type = self.sequelize.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) {
// 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.');
}
}
attribute = self.sequelize.normalizeAttribute(attribute);
return attribute;
});
......
......@@ -1308,9 +1308,27 @@ Sequelize.prototype.normalizeAttribute = function(attribute) {
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) {
// 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;
......
......@@ -2,6 +2,7 @@
/* jshint -W110 */
var dataTypes = require('./data-types')
, _ = require('lodash')
, SqlString = exports;
SqlString.escapeId = function(val, forbidQualified) {
......@@ -29,11 +30,19 @@ SqlString.escape = function(val, timeZone, dialect) {
}
if (val instanceof Date) {
val = dataTypes[dialect].DATE.prototype.stringify(val, { options: timeZone });
val = dataTypes[dialect].DATE.prototype.stringify(val, { timezone: timeZone });
}
if (Buffer.isBuffer(val)) {
return dataTypes[dialect].BLOB.prototype.stringify(val);
if (dataTypes[dialect].BLOB) {
return dataTypes[dialect].BLOB.prototype.stringify(val);
}
return dataTypes.BLOB.prototype.stringify(val);
}
if (Array.isArray(val) && dialect === 'postgres') {
return dataTypes.ARRAY.prototype.stringify(val, { escape: _.partialRight(SqlString.escape, timeZone, dialect) });
}
if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') {
......
......@@ -7,12 +7,39 @@ var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, sinon = require('sinon')
, dataTypes = require('../../lib/data-types')
, _ = 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();
}
});
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');
......@@ -44,58 +71,204 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
expect(stringify).to.have.been.called;
expect(moment.isMoment(user.dateField)).to.be.ok;
delete Sequelize.DATE.parse;
});
});
var testType = function (Type, value, options) {
options = options || {};
it('calls parse and stringify for ' + Type.toSql(), function () {
var parse = Type.constructor.parse = sinon.spy(options.parse || function (value) {
return value;
});
var testSuccess = function (Type, value) {
var parse = Type.constructor.parse = sinon.spy(function (value) {
return value;
});
var stringify = Type.constructor.prototype.stringify = sinon.spy(options.$stringify || function (value) {
return value;
});
var stringify = Type.constructor.prototype.stringify = sinon.spy(function (value) {
return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments);
});
current.refreshTypes();
current.refreshTypes();
var User = current.define('user', {
field: Type
}, {
timestamps: false
});
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;
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;
});
};
// [new Sequelize.TEXT(), new Sequelize.STRING(), new Sequelize.CHAR(), new Sequelize.CHAR().BINARY].forEach(function (Type) {
// testType(Type, 'foobar');
// });
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();
// [new Sequelize.BOOLEAN(), new Sequelize.DOUBLE(), new Sequelize.REAL(), new Sequelize.INTEGER(), new Sequelize.DECIMAL()].forEach(function (Type) {
// testType(Type, 1);
// });
return testSuccess(Type, 'foobar');
});
// [new Sequelize.REAL(), new Sequelize.FLOAT()].forEach(function (Type) {
// testType(Type, 1);
// });
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);
});
var blobStringify = ('BLOB' in dataTypes[dialect]) ? dataTypes[dialect].BLOB : dataTypes.BLOB;
blobStringify = blobStringify.prototype.$stringify;
// testType(new Sequelize.BLOB(), new Buffer('hej'), { $stringify: blobStringify });
testType(new Sequelize.STRING().BINARY, 'foo');
it('calls parse and stringify for DECIMAL', function () {
var Type = new Sequelize.DECIMAL();
var stringBinary = new Sequelize.STRING().BINARY;
testType(stringBinary, new Buffer('foo'), { $stringify: blobStringify.bind(stringBinary)});
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'].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()) {
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);
}
QueryGenerator.options = context.options;
QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect;
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation);
......
......@@ -20,6 +20,7 @@ if (dialect.match(/^postgres/)) {
numbers: { type: DataTypes.ARRAY(DataTypes.FLOAT) },
document: { type: DataTypes.HSTORE, defaultValue: { default: '"value"' } }
});
return this.User.sync({ force: true });
});
......@@ -548,7 +549,7 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {data: new Buffer('Sequelize') }],
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]);"
}, {
arguments: ['myTable', {name: 'foo', foo: 1}],
......@@ -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');",
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]);",
context: {options: {quoteIdentifiers: false}}
}, {
......@@ -744,7 +745,7 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {bar: 2}, {name: 'foo'}, { returning: true }],
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'"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
......@@ -802,7 +803,7 @@ if (dialect.match(/^postgres/)) {
expectation: "UPDATE myTable SET bar=2 WHERE name = 'foo'",
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'",
context: {options: {quoteIdentifiers: false}}
}, {
......@@ -942,11 +943,12 @@ if (dialect.match(/^postgres/)) {
it(title, function() {
// 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: {}};
if (test.needsSequelize) {
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);
}
QueryGenerator.options = context.options;
QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect;
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation);
......
......@@ -57,8 +57,9 @@ if (dialect.match(/^postgres/)) {
});
it('should handle date values', function () {
expect(range.stringify([new Date(Date.UTC(2000, 1, 1)),
new Date(Date.UTC(2000, 1, 2))])).to.equal('("2000-02-01T00:00:00.000Z","2000-02-02T00:00:00.000Z")');
var Range = new DataTypes.postgres.RANGE(DataTypes.DATE);
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/)) {
var testRange = [5,10];
testRange.inclusive = [true, true];
expect(range.parse(range.stringify(testRange), DataTypes.RANGE(DataTypes.INTEGER))).to.deep.equal(testRange);
expect(range.parse(range.stringify(range.parse(range.stringify(testRange), DataTypes.RANGE(DataTypes.INTEGER))), DataTypes.RANGE(DataTypes.INTEGER))).to.deep.equal(testRange);
var Range = new DataTypes.postgres.RANGE(DataTypes.INTEGER);
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);
});
});
});
......
......@@ -529,7 +529,7 @@ if (dialect === 'sqlite') {
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);
}
QueryGenerator.options = context.options;
QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect;
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation);
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!