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

Commit aadec576 by Sascha Depold

Merge branch 'master' of github.com:sequelize/sequelize

2 parents 4df4b9b1 3c14f562
# v1.7.0 # # v1.7.0 #
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango - [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango - [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango
- [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx - [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics - [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango - [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango
...@@ -12,6 +13,10 @@ ...@@ -12,6 +13,10 @@
- [BUG] Default ports are now declared in the connector manager, which means the default port for PG correctly becomes 5432. [#633](https://github.com/sequelize/sequelize/issues/633). durango - [BUG] Default ports are now declared in the connector manager, which means the default port for PG correctly becomes 5432. [#633](https://github.com/sequelize/sequelize/issues/633). durango
- [BUG] Columns with type BOOLEAN were always added to toJSON output, even if they were not selected [see](https://gist.github.com/gchaincl/45aca14e93934bf4a05e). janmeier - [BUG] Columns with type BOOLEAN were always added to toJSON output, even if they were not selected [see](https://gist.github.com/gchaincl/45aca14e93934bf4a05e). janmeier
- [BUG] Hstore is now fully supported [#695](https://github.com/sequelize/sequelize/pull/695). thanks to tadman - [BUG] Hstore is now fully supported [#695](https://github.com/sequelize/sequelize/pull/695). thanks to tadman
- [BUG] Correct join table name for tables with custom names [#698](https://github.com/sequelize/sequelize/pull/698). thanks to jjclark1982
- [BUG] PostgreSQL should now be able to insert empty arrays with typecasting. [#718](https://github.com/sequelize/sequelize/pull/718). thanks to durango
- [BUG] Fields should be escaped by quoteIdentifier for max/min functions which allows SQL reserved keywords to be used. [#719](https://github.com/sequelize/sequelize/pull/719). thanks to durango
- [BUG] Fixed bug when trying to save objects with eagerly loaded attributes [#716](https://github.com/sequelize/sequelize/pull/716). thanks to iamjochen
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango - [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
...@@ -26,6 +31,10 @@ ...@@ -26,6 +31,10 @@
- [FEATURE] Field and table comments for MySQL and PG. [#523](https://github.com/sequelize/sequelize/pull/523). MySQL by iamjochen. PG by janmeier - [FEATURE] Field and table comments for MySQL and PG. [#523](https://github.com/sequelize/sequelize/pull/523). MySQL by iamjochen. PG by janmeier
- [FEATURE] BigInts can now be used for autoincrement/serial columns. [#673](https://github.com/sequelize/sequelize/pull/673). thanks to sevastos - [FEATURE] BigInts can now be used for autoincrement/serial columns. [#673](https://github.com/sequelize/sequelize/pull/673). thanks to sevastos
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier - [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [FEATURE] Use moment for better postgres timestamp strings. [#710](https://github.com/sequelize/sequelize/pull/710). Thanks to seth-admittedly
- [FEATURE] Keep milliseconds in timestamps for postgres. [#712](https://github.com/sequelize/sequelize/pull/712). Thanks to seth-admittedly
- [FEATURE] You can now set lingo's language through Sequelize. [#713](https://github.com/sequelize/sequelize/pull/713). Thanks to durango
- [FEATURE] Added a `findAndCountAll`, useful for pagination. [#533](https://github.com/sequelize/sequelize/pull/533). Thanks to iamjochen
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
...@@ -11,7 +11,7 @@ module.exports = (function() { ...@@ -11,7 +11,7 @@ module.exports = (function() {
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) { if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.source.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.source.options.language) + "Id", this.source.options.underscored)
} }
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
...@@ -23,7 +23,7 @@ module.exports = (function() { ...@@ -23,7 +23,7 @@ module.exports = (function() {
BelongsTo.prototype.injectAttributes = function() { BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName, this.target.options.language) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
...@@ -36,7 +36,7 @@ module.exports = (function() { ...@@ -36,7 +36,7 @@ module.exports = (function() {
BelongsTo.prototype.injectGetter = function(obj) { BelongsTo.prototype.injectGetter = function(obj) {
var self = this var self = this
, accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))) , accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
obj[accessor] = function(params) { obj[accessor] = function(params) {
var id = this[self.identifier] var id = this[self.identifier]
...@@ -59,7 +59,7 @@ module.exports = (function() { ...@@ -59,7 +59,7 @@ module.exports = (function() {
BelongsTo.prototype.injectSetter = function(obj) { BelongsTo.prototype.injectSetter = function(obj) {
var self = this var self = this
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) , accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
obj[accessor] = function(associatedObject) { obj[accessor] = function(associatedObject) {
this[self.identifier] = associatedObject ? associatedObject.id : null this[self.identifier] = associatedObject ? associatedObject.id : null
......
...@@ -19,15 +19,16 @@ module.exports = (function() { ...@@ -19,15 +19,16 @@ module.exports = (function() {
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
) )
this.associationAccessor = this.combinedName = (this.options.joinTableName || combinedTableName) this.associationAccessor = this.combinedName = (this.options.joinTableName || combinedTableName)
this.options.tableName = this.combinedName
var as = (this.options.as || Utils.pluralize(this.target.tableName)) var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + as), get: Utils._.camelize('get_' + as),
set: Utils._.camelize('set_' + as), set: Utils._.camelize('set_' + as),
add: Utils._.camelize(Utils.singularize('add_' + as)), add: Utils._.camelize(Utils.singularize('add_' + as, this.target.options.language)),
remove: Utils._.camelize(Utils.singularize('remove_' + as)), remove: Utils._.camelize(Utils.singularize('remove_' + as, this.target.options.language)),
hasSingle: Utils._.camelize(Utils.singularize('has_' + as)), hasSingle: Utils._.camelize(Utils.singularize('has_' + as, this.target.options.language)),
hasAll: Utils._.camelize('has_' + as) hasAll: Utils._.camelize('has_' + as)
} }
} }
...@@ -36,7 +37,7 @@ module.exports = (function() { ...@@ -36,7 +37,7 @@ module.exports = (function() {
// or in an extra table which connects two tables // or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() { HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor)
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
// is there already a single sided association between the source and the target? // is there already a single sided association between the source and the target?
// or is the association on the model itself? // or is the association on the model itself?
......
...@@ -11,7 +11,7 @@ module.exports = (function() { ...@@ -11,7 +11,7 @@ module.exports = (function() {
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) { if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.target.options.language) + "Id", this.options.underscored)
} }
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
...@@ -19,8 +19,8 @@ module.exports = (function() { ...@@ -19,8 +19,8 @@ module.exports = (function() {
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))), get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))),
set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
} }
} }
...@@ -28,7 +28,7 @@ module.exports = (function() { ...@@ -28,7 +28,7 @@ module.exports = (function() {
HasOne.prototype.injectAttributes = function() { HasOne.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
......
...@@ -18,7 +18,8 @@ module.exports = (function() { ...@@ -18,7 +18,8 @@ module.exports = (function() {
paranoid: false, paranoid: false,
whereCollection: null, whereCollection: null,
schema: null, schema: null,
schemaDelimiter: '' schemaDelimiter: '',
language: 'en'
}, options || {}) }, options || {})
// error check options // error check options
...@@ -31,7 +32,7 @@ module.exports = (function() { ...@@ -31,7 +32,7 @@ module.exports = (function() {
this.name = name this.name = name
if (!this.options.tableName) { if (!this.options.tableName) {
this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name) this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name, this.options.language)
} else { } else {
this.tableName = this.options.tableName this.tableName = this.options.tableName
} }
...@@ -296,16 +297,61 @@ module.exports = (function() { ...@@ -296,16 +297,61 @@ module.exports = (function() {
return this.QueryInterface.rawSelect(this.getTableName(), options, 'count') return this.QueryInterface.rawSelect(this.getTableName(), options, 'count')
} }
DAOFactory.prototype.findAndCountAll = function(options) {
var self = this
, opts = Utils._.cloneDeep(options)
, copts = Utils._.extend({}, Utils._.cloneDeep(options) || {}, {
// no limit, offset, order or include for the options given to count()
offset : 0,
limit : 0,
order : null,
include : null
})
return new Utils.CustomEventEmitter(function (emitter) {
var emit = {
err : function(e) { // emit error
emitter.emit('error', e);
}
, okay : function(c, r) { // emit success
emitter.emit('success', {
count: c || 0,
rows : (r && Array.isArray(r) ? r : [])
});
}
, sql : function(s) { // emit SQL
emitter.emit('sql', s);
}
}
self.count(copts)
.on('sql', emit.sql)
.error(emit.err)
.success(function(cnt) {
if (cnt === 0)
return emit.okay(cnt) // no records, no need for another query
self.findAll(options)
.on('sql', emit.sql)
.error(emit.err)
.success(function(rows) {
emit.okay(cnt, rows)
})
})
}).run()
}
DAOFactory.prototype.max = function(field, options) { DAOFactory.prototype.max = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['max(' + field + ')', 'max']) options.attributes.push(['max(' + this.QueryInterface.QueryGenerator.quoteIdentifier(field) + ')', 'max'])
options.parseFloat = true options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'max') return this.QueryInterface.rawSelect(this.getTableName(), options, 'max')
} }
DAOFactory.prototype.min = function(field, options) { DAOFactory.prototype.min = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['min(' + field + ')', 'min']) options.attributes.push(['min(' + this.QueryInterface.QueryGenerator.quoteIdentifier(field) + ')', 'min'])
options.parseFloat = true options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'min') return this.QueryInterface.rawSelect(this.getTableName(), options, 'min')
...@@ -395,7 +441,7 @@ module.exports = (function() { ...@@ -395,7 +441,7 @@ module.exports = (function() {
daos.forEach(function(dao) { daos.forEach(function(dao) {
var values = {}; var values = {};
fields.forEach(function(field) { fields.forEach(function(field) {
values[field] = dao.values[field] values[field] = dao.dataValues[field]
}) })
if (self.options.timestamps) { if (self.options.timestamps) {
values[updatedAtAttr] = Utils.now() values[updatedAtAttr] = Utils.now()
...@@ -405,7 +451,7 @@ module.exports = (function() { ...@@ -405,7 +451,7 @@ module.exports = (function() {
} else { } else {
daos.forEach(function(dao) { daos.forEach(function(dao) {
records.push(dao.values) records.push(dao.dataValues)
}) })
} }
......
...@@ -35,9 +35,9 @@ var validateAttributes = function() { ...@@ -35,9 +35,9 @@ var validateAttributes = function() {
var errors = {} var errors = {}
// for each field and value // for each field and value
Utils._.each(this.model.values, function(value, field) { Utils._.each(this.model.dataValues, function(value, field) {
var rawAttribute = this.model.rawAttributes[field] var rawAttribute = this.model.rawAttributes[field]
, hasAllowedNull = ((rawAttribute.allowNull === true) && ((value === null) || (value === undefined))) , hasAllowedNull = ((rawAttribute === undefined || rawAttribute.allowNull === true) && ((value === null) || (value === undefined)))
if (this.model.validators.hasOwnProperty(field) && !hasAllowedNull) { if (this.model.validators.hasOwnProperty(field) && !hasAllowedNull) {
errors = Utils._.merge(errors, validateAttribute.call(this, value, field)) errors = Utils._.merge(errors, validateAttribute.call(this, value, field))
......
...@@ -93,7 +93,7 @@ module.exports = (function() { ...@@ -93,7 +93,7 @@ module.exports = (function() {
// only those fields will be updated // only those fields will be updated
DAO.prototype.save = function(fields) { DAO.prototype.save = function(fields) {
var self = this var self = this
, values = fields ? {} : this.values , values = fields ? {} : this.dataValues
, updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt' , updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt'
...@@ -108,7 +108,7 @@ module.exports = (function() { ...@@ -108,7 +108,7 @@ module.exports = (function() {
} }
} }
var tmpVals = self.values var tmpVals = self.dataValues
fields.forEach(function(field) { fields.forEach(function(field) {
if (tmpVals[field] !== undefined) { if (tmpVals[field] !== undefined) {
...@@ -138,7 +138,7 @@ module.exports = (function() { ...@@ -138,7 +138,7 @@ module.exports = (function() {
} }
if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) { if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) {
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now() this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(this.sequelize.options.dialect)
} }
var errors = this.validate() var errors = this.validate()
...@@ -279,7 +279,7 @@ module.exports = (function() { ...@@ -279,7 +279,7 @@ module.exports = (function() {
DAO.prototype.equals = function(other) { DAO.prototype.equals = function(other) {
var result = true var result = true
Utils._.each(this.values, function(value, key) { Utils._.each(this.dataValues, function(value, key) {
if(Utils._.isDate(value) && Utils._.isDate(other[key])) { if(Utils._.isDate(value) && Utils._.isDate(other[key])) {
result = result && (value.getTime() == other[key].getTime()) result = result && (value.getTime() == other[key].getTime())
} else { } else {
...@@ -369,8 +369,8 @@ module.exports = (function() { ...@@ -369,8 +369,8 @@ module.exports = (function() {
} }
if (this.__options.timestamps) { if (this.__options.timestamps) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now() defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now(this.sequelize.options.dialect)
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now() defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now(this.sequelize.options.dialect)
if (this.__options.paranoid) { if (this.__options.paranoid) {
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null
......
...@@ -302,11 +302,12 @@ module.exports = (function() { ...@@ -302,11 +302,12 @@ module.exports = (function() {
var buildAssociatedDaoInstances = function(tableName, associationData, dao) { var buildAssociatedDaoInstances = function(tableName, associationData, dao) {
var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' }) var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' })
, association = null , association = null
, self = this
if (!!associatedDaoFactory) { if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory) association = this.callee.getAssociation(associatedDaoFactory)
} else { } else {
associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(Utils.pluralize(tableName), { attribute: 'tableName' }) associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(Utils.pluralize(tableName, this.sequelize.language), { attribute: 'tableName' })
if (!!associatedDaoFactory) { if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory) association = this.callee.getAssociation(associatedDaoFactory)
...@@ -326,7 +327,7 @@ module.exports = (function() { ...@@ -326,7 +327,7 @@ module.exports = (function() {
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers) , isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) { if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
accessor = Utils.singularize(accessor) accessor = Utils.singularize(accessor, self.sequelize.language)
dao[accessor] = isEmpty ? null : daoInstance dao[accessor] = isEmpty ? null : daoInstance
} else { } else {
dao[accessor] = dao[accessor] || [] dao[accessor] = dao[accessor] || []
......
...@@ -241,7 +241,9 @@ module.exports = (function() { ...@@ -241,7 +241,9 @@ module.exports = (function() {
} }
var connect = function(done, config) { var connect = function(done, config) {
var emitter = new (require('events').EventEmitter)()
config = config || this.config config = config || this.config
var connection = mysql.createConnection({ var connection = mysql.createConnection({
host: config.host, host: config.host,
port: config.port, port: config.port,
...@@ -250,6 +252,23 @@ module.exports = (function() { ...@@ -250,6 +252,23 @@ module.exports = (function() {
database: config.database, database: config.database,
timezone: 'Z' timezone: 'Z'
}) })
connection.connect(function(err) {
if (err) {
switch(err.code) {
case 'ECONNREFUSED':
case 'ER_ACCESS_DENIED_ERROR':
emitter.emit('error', new Error("Failed to authenticate for MySQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', new Error("Failed to find MySQL server. Please double check your settings."))
break
}
}
})
connection.query("SET time_zone = '+0:00'"); connection.query("SET time_zone = '+0:00'");
// client.setMaxListeners(self.maxConcurrentQueries) // client.setMaxListeners(self.maxConcurrentQueries)
this.isConnecting = false this.isConnecting = false
......
...@@ -27,7 +27,8 @@ module.exports = (function() { ...@@ -27,7 +27,8 @@ module.exports = (function() {
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
if (this.client == null) {
if (this.client === null) {
this.connect() this.connect()
} }
...@@ -68,7 +69,18 @@ module.exports = (function() { ...@@ -68,7 +69,18 @@ module.exports = (function() {
self.isConnecting = false self.isConnecting = false
if (!!err) { if (!!err) {
emitter.emit('error', err) switch(err.code) {
case 'ECONNREFUSED':
emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', new Error("Failed to find PostgresSQL server. Please double check your settings."))
break
default:
emitter.emit('error', err)
}
} else if (client) { } else if (client) {
client.query("SET TIME ZONE 'UTC'") client.query("SET TIME ZONE 'UTC'")
.on('end', function() { .on('end', function() {
......
...@@ -3,6 +3,7 @@ var Utils = require("../../utils") ...@@ -3,6 +3,7 @@ var Utils = require("../../utils")
, DataTypes = require("../../data-types") , DataTypes = require("../../data-types")
, tables = {} , tables = {}
, primaryKeys = {} , primaryKeys = {}
, moment = require("moment")
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
...@@ -288,7 +289,7 @@ module.exports = (function() { ...@@ -288,7 +289,7 @@ module.exports = (function() {
return Utils._.template(query)(options) return Utils._.template(query)(options)
}, },
insertQuery: function(tableName, attrValueHash) { insertQuery: function(tableName, attrValueHash, attributes) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;" var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
...@@ -305,14 +306,17 @@ module.exports = (function() { ...@@ -305,14 +306,17 @@ module.exports = (function() {
} }
}); });
var rowValues = []
Object.keys(attrValueHash).forEach(function(attr) {
rowValues[rowValues.length] = this.escape(attrValueHash[attr], (!!attributes && !!attributes[attr] ? attributes[attr] : undefined))
}.bind(this))
var replacements = { var replacements = {
table: this.quoteIdentifiers(tableName) table: this.quoteIdentifiers(tableName)
, attributes: Object.keys(attrValueHash).map(function(attr){ , attributes: Object.keys(attrValueHash).map(function(attr){
return this.quoteIdentifier(attr) return this.quoteIdentifier(attr)
}.bind(this)).join(",") }.bind(this)).join(",")
, values: Utils._.values(attrValueHash).map(function(value){ , values: rowValues.join(",")
return this.escape(value)
}.bind(this)).join(",")
} }
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
...@@ -495,7 +499,7 @@ module.exports = (function() { ...@@ -495,7 +499,7 @@ module.exports = (function() {
result = smth result = smth
} }
else if (Array.isArray(smth)) { else if (Array.isArray(smth)) {
result = Utils.format(smth) result = Utils.format(smth, "postgres")
} }
return result return result
...@@ -680,9 +684,7 @@ module.exports = (function() { ...@@ -680,9 +684,7 @@ module.exports = (function() {
}, },
pgSqlDate: function (dt) { pgSqlDate: function (dt) {
var date = [ dt.getUTCFullYear(), this.padInt(dt.getUTCMonth()+1), this.padInt(dt.getUTCDate()) ].join('-') return moment(dt).format("YYYY-MM-DD HH:mm:ss.SSS Z")
var time = [ dt.getUTCHours(), this.padInt(dt.getUTCMinutes()), this.padInt(dt.getUTCSeconds())].join(':')
return date + ' ' + time + '.' + ((dt.getTime() % 1000) * 1000) + 'Z'
}, },
pgDataTypeMapping: function (tableName, attr, dataType) { pgDataTypeMapping: function (tableName, attr, dataType) {
...@@ -735,7 +737,7 @@ module.exports = (function() { ...@@ -735,7 +737,7 @@ module.exports = (function() {
return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.') return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.')
}, },
escape: function (val) { escape: function (val, field) {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
...@@ -747,7 +749,11 @@ module.exports = (function() { ...@@ -747,7 +749,11 @@ module.exports = (function() {
return val + ''; return val + '';
case 'object': case 'object':
if (Array.isArray(val)) { if (Array.isArray(val)) {
return 'ARRAY['+ val.map(function(it) { return this.escape(it) }.bind(this)).join(',') + ']'; var ret = 'ARRAY['+ val.map(function(it) { return this.escape(it) }.bind(this)).join(',') + ']'
if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, '')
}
return ret
} }
} }
......
...@@ -5,17 +5,34 @@ var Utils = require("../../utils") ...@@ -5,17 +5,34 @@ var Utils = require("../../utils")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
this.database = db = new sqlite3.Database(sequelize.options.storage || ':memory:', function(err) { }
if(!err && sequelize.options.foreignKeys !== false) {
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.connect = function() {
var emitter = new (require('events').EventEmitter)()
, self = this
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) {
if (err.code === "SQLITE_CANTOPEN") {
emitter.emit('error', new Error("Failed to find SQL server. Please double check your settings."))
}
}
if(!err && self.sequelize.options.foreignKeys !== false) {
// Make it possible to define and use foreign key constraints unless // Make it possible to define and use foreign key constraints unless
// explicitly disallowed. It's still opt-in per relation // explicitly disallowed. It's still opt-in per relation
db.run('PRAGMA FOREIGN_KEYS=ON') db.run('PRAGMA FOREIGN_KEYS=ON')
} }
}) })
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
if (!this.database) {
this.connect()
}
return new Query(this.database, this.sequelize, callee, options).run(sql) return new Query(this.database, this.sequelize, callee, options).run(sql)
} }
......
...@@ -254,7 +254,7 @@ module.exports = (function() { ...@@ -254,7 +254,7 @@ module.exports = (function() {
} }
QueryInterface.prototype.insert = function(dao, tableName, values) { QueryInterface.prototype.insert = function(dao, tableName, values) {
var sql = this.QueryGenerator.insertQuery(tableName, values) var sql = this.QueryGenerator.insertQuery(tableName, values, dao.daoFactory.rawAttributes)
return queryAndEmit.call(this, [sql, dao], 'insert', { return queryAndEmit.call(this, [sql, dao], 'insert', {
success: function(obj) { obj.isNewRecord = false } success: function(obj) { obj.isNewRecord = false }
}) })
......
...@@ -79,7 +79,8 @@ module.exports = (function() { ...@@ -79,7 +79,8 @@ module.exports = (function() {
native: false, native: false,
replication: false, replication: false,
pool: {}, pool: {},
quoteIdentifiers: true quoteIdentifiers: true,
language: 'en'
}, options || {}) }, options || {})
if (this.options.logging === true) { if (this.options.logging === true) {
...@@ -172,7 +173,9 @@ module.exports = (function() { ...@@ -172,7 +173,9 @@ module.exports = (function() {
} }
}) })
} }
options.omitNull = globalOptions.omitNull options.omitNull = globalOptions.omitNull
options.language = globalOptions.language
// if you call "define" multiple times for the same daoName, do not clutter the factory // if you call "define" multiple times for the same daoName, do not clutter the factory
if(this.isDefined(daoName)) { if(this.isDefined(daoName)) {
......
...@@ -72,12 +72,12 @@ var Utils = module.exports = { ...@@ -72,12 +72,12 @@ var Utils = module.exports = {
return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1) return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1)
}, },
singularize: function(s) { singularize: function(s, language) {
return Utils.Lingo.en.isSingular(s) ? s : Utils.Lingo.en.singularize(s) return Utils.Lingo[language || 'en'].isSingular(s) ? s : Utils.Lingo[language || 'en'].singularize(s)
}, },
pluralize: function(s) { pluralize: function(s, language) {
return Utils.Lingo.en.isPlural(s) ? s : Utils.Lingo.en.pluralize(s) return Utils.Lingo[language || 'en'].isPlural(s) ? s : Utils.Lingo[language || 'en'].pluralize(s)
}, },
removeCommentsFromFunctionString: function(s) { removeCommentsFromFunctionString: function(s) {
...@@ -164,9 +164,9 @@ var Utils = module.exports = { ...@@ -164,9 +164,9 @@ var Utils = module.exports = {
return subClass; return subClass;
}, },
now: function() { now: function(dialect) {
var now = new Date() var now = new Date()
now.setMilliseconds(0) if(dialect != "postgres") now.setMilliseconds(0)
return now return now
}, },
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
"jasmine-node": "1.5.0", "jasmine-node": "1.5.0",
"sqlite3": "~2.1.5", "sqlite3": "~2.1.5",
"mysql": "~2.0.0-alpha7", "mysql": "~2.0.0-alpha7",
"pg": "~0.10.2", "pg": "~2.0.0",
"buster": "~0.6.3", "buster": "~0.6.3",
"watchr": "~2.2.0", "watchr": "~2.2.0",
"yuidocjs": "~0.3.36" "yuidocjs": "~0.3.36"
......
...@@ -208,6 +208,74 @@ describe('DAOFactory', function() { ...@@ -208,6 +208,74 @@ describe('DAOFactory', function() {
}) })
}) })
describe('findAndCountAll', function() {
var users = [], fullcount = 3
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, fullcount)
})
it("handles where clause [only]", function() {
Helpers.async(function(done) {
User.findAndCountAll({where: "id != " + users[0].id}).success(function(info) {
expect(info.count).toEqual(fullcount - 1)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
it("handles where clause with ordering [only]", function() {
Helpers.async(function(done) {
User.findAndCountAll({where: "id != " + users[0].id, order: 'id ASC'}).success(function(info) {
expect(info.count).toEqual(fullcount - 1)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
/*
// at time of writing (v1.6.0) Sequelize does not seem to support 'offset' on it's own consistently (goes wrong for PostGRES and SQLite)
it("handles offset", function() {
Helpers.async(function(done) {
User.findAndCountAll({offset: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
*/
it("handles limit", function() {
Helpers.async(function(done) {
User.findAndCountAll({limit: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(1)
done()
})
})
})
it("handles offset and limit", function() {
Helpers.async(function(done) {
User.findAndCountAll({offset: 1, limit: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(1)
done()
})
})
})
})
describe('all', function() { describe('all', function() {
beforeEach(function() { beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2) Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
......
...@@ -338,7 +338,7 @@ describe('QueryGenerator', function() { ...@@ -338,7 +338,7 @@ describe('QueryGenerator', function() {
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.0Z') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;"
...@@ -379,7 +379,7 @@ describe('QueryGenerator', function() { ...@@ -379,7 +379,7 @@ describe('QueryGenerator', function() {
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}],
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.0Z') RETURNING *;", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
...@@ -426,7 +426,7 @@ describe('QueryGenerator', function() { ...@@ -426,7 +426,7 @@ describe('QueryGenerator', function() {
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.0Z'),('bar','2012-03-27 10:01:55.0Z') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2) RETURNING *;"
...@@ -467,7 +467,7 @@ describe('QueryGenerator', function() { ...@@ -467,7 +467,7 @@ describe('QueryGenerator', function() {
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]],
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.0Z'),('bar','2012-03-27 10:01:55.0Z') RETURNING *;", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]],
...@@ -507,10 +507,10 @@ describe('QueryGenerator', function() { ...@@ -507,10 +507,10 @@ describe('QueryGenerator', function() {
updateQuery: [ updateQuery: [
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0Z' WHERE \"id\"=2 RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2],
expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0Z' WHERE \"id\"=2 RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *"
...@@ -534,7 +534,7 @@ describe('QueryGenerator', function() { ...@@ -534,7 +534,7 @@ describe('QueryGenerator', function() {
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0Z' WHERE \"id\"=2 RETURNING *" expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *"
...@@ -543,11 +543,11 @@ describe('QueryGenerator', function() { ...@@ -543,11 +543,11 @@ describe('QueryGenerator', function() {
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.0Z' WHERE id=2 RETURNING *", expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2],
expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.0Z' WHERE id=2 RETURNING *", expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
...@@ -575,7 +575,7 @@ describe('QueryGenerator', function() { ...@@ -575,7 +575,7 @@ describe('QueryGenerator', function() {
context: {options: {omitNull: true, quoteIdentifiers: false}}, context: {options: {omitNull: true, quoteIdentifiers: false}},
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.0Z' WHERE id=2 RETURNING *", expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}],
......
...@@ -372,7 +372,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -372,7 +372,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }, {timestamps: false}) this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }, {timestamps: false})
this.User.hasMany(this.Task) this.User.hasMany(this.Task)
this.Task.hasMany(this.User) this.Task.hasMany(this.User)
this.sequelize.sync({force: true}).success(done) this.sequelize.sync({force: true}).success(done)
}) })
...@@ -385,7 +385,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -385,7 +385,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.Task.create({ title: 'task2' }).success(function(task2) { this.Task.create({ title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) { user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) {
expect(sql).toMatch("INSERT INTO") expect(sql).toMatch("INSERT INTO")
expect(sql).toMatch("VALUES (1,1),(2,1)") expect(sql).toMatch("VALUES (1,1),(1,2)")
})).success(function () { })).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for INSERT into expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for INSERT into
done() done()
...@@ -415,6 +415,43 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -415,6 +415,43 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
}.bind(this)) }.bind(this))
}) })
}) // end optimization using bulk create, destroy and update }) // end optimization using bulk create, destroy and update
describe('join table creation', function () {
before(function (done) {
this.User = this.sequelize.define('User',
{ username: Sequelize.STRING },
{ tableName: 'users'}
)
this.Task = this.sequelize.define('Task',
{ title: Sequelize.STRING },
{ tableName: 'tasks' }
)
this.User.hasMany(this.Task,
{ joinTableName: 'user_has_tasks' }
)
this.Task.hasMany(this.User)
this.sequelize.sync({force: true}).success(done)
})
it('uses the specified joinTableName or a reasonable default', function(done) {
for (var associationName in this.User.associations) {
expect(associationName).not.toEqual(this.User.tableName)
expect(associationName).not.toEqual(this.Task.tableName)
var joinTableName = this.User.associations[associationName].options.joinTableName
if (typeof joinTableName !== 'undefined') {
expect(joinTableName).toEqual(associationName)
}
var tableName = this.User.associations[associationName].options.tableName
if (typeof tableName !== 'undefined') {
expect(tableName).toEqual(associationName)
}
}
done()
})
})
}) })
describe("Foreign key constraints", function() { describe("Foreign key constraints", function() {
...@@ -572,4 +609,4 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -572,4 +609,4 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
}) })
\ No newline at end of file
if(typeof require === 'function') { if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter") , CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect() , config = require(__dirname + "/config/config")
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
...@@ -11,6 +12,86 @@ buster.testRunner.timeout = 1000 ...@@ -11,6 +12,86 @@ buster.testRunner.timeout = 1000
var Sequelize = require(__dirname + '/../index') var Sequelize = require(__dirname + '/../index')
describe(Helpers.getTestDialectTeaser("Configuration"), function() { describe(Helpers.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() {
it('should give us an error for not having the correct server details', function(done) {
var domain
try {
domain = require('domain')
} catch (err) {
console.log('WARNING: Configuration specs requires Node version >= 0.8')
expect('').toEqual('') // Silence Buster!
done()
}
var d = domain.create()
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect})
d.add(sequelize.query)
d.on('error', function(err){
d.remove(sequelize.query)
var msg = 'Failed to find SQL server. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to find PostgresSQL server. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to find MySQL server. Please double check your settings.'
}
expect(err.message).toEqual(msg)
done()
})
d.run(function(){
sequelize.query('select 1 as hello')
.success(function(){})
})
})
it('should give us an error for not having the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native" && dialect !== "mysql") {
// This dialect doesn't support incorrect login information
expect('').toEqual('') // Silence Buster
return done()
}
var domain
try {
domain = require('domain')
} catch (err) {
console.log('WARNING: Configuration specs requires Node version >= 0.8')
expect('').toEqual('') // Silence Buster!
done()
}
var d = domain.create()
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
d.add(sequelize.query)
d.on('error', function(err){
d.remove(sequelize.query)
var msg = 'Failed to authenticate for SQL. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to authenticate for PostgresSQL. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to authenticate for MySQL. Please double check your settings.'
}
expect(err.message).toEqual(msg)
done()
})
d.run(function(){
sequelize.query('select 1 as hello')
.success(function(){})
})
})
})
describe('Instantiation with a URL string', function() { describe('Instantiation with a URL string', function() {
it('should accept username, password, host, port, and database', function() { it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname') var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
......
...@@ -281,6 +281,26 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -281,6 +281,26 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}) })
describe('create', function() { describe('create', function() {
it("casts empty arrays correctly for postgresql", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').toEqual('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [], mystr: []}).on('sql', function(sql){
expect(sql.indexOf('ARRAY[]::INTEGER[]')).toBeGreaterThan(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).toBeGreaterThan(-1)
done()
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) { it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', { var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true } username: { type: Sequelize.STRING, unique: true }
...@@ -357,7 +377,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -357,7 +377,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
User.create({ big: '9223372036854775807' }).on('success', function(user) { User.create({ big: '9223372036854775807' }).on('success', function(user) {
expect(user.big).toEqual( '9223372036854775807' ) expect(user.big).toBe( '9223372036854775807' )
done() done()
}) })
}) })
...@@ -984,8 +1004,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -984,8 +1004,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
] ]
, done = _.after(2 * permutations.length, _done); , done = _.after(2 * permutations.length, _done);
this.User.create({name: 'jack'}).success(function (jack) { this.User.create({username: 'jack'}).success(function (jack) {
this.User.create({name: 'jill'}).success(function (jill) { this.User.create({username: 'jill'}).success(function (jill) {
permutations.forEach(function(perm) { permutations.forEach(function(perm) {
this.User.find(perm).done(function(err, user) { this.User.find(perm).done(function(err, user) {
expect(err).toBeNull(); expect(err).toBeNull();
...@@ -1635,7 +1655,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1635,7 +1655,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
describe('max', function() { describe('max', function() {
before(function(done) { before(function(done) {
this.UserWithAge = this.sequelize.define('UserWithAge', { this.UserWithAge = this.sequelize.define('UserWithAge', {
age: Sequelize.INTEGER age: Sequelize.INTEGER,
order: Sequelize.INTEGER
}) })
this.UserWithDec = this.sequelize.define('UserWithDec', { this.UserWithDec = this.sequelize.define('UserWithDec', {
...@@ -1647,6 +1668,15 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1647,6 +1668,15 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}.bind(this)) }.bind(this))
}) })
it("should return the max value for a field named the same as an SQL reserved keyword", function(done) {
this.UserWithAge.create({age: 3, order: 5}).success(function(){
this.UserWithAge.max('order').success(function(max) {
expect(max).toEqual(5)
done()
})
}.bind(this))
})
it("should return the max value", function(done) { it("should return the max value", function(done) {
this.UserWithAge.create({ age: 2 }).success(function() { this.UserWithAge.create({ age: 2 }).success(function() {
this.UserWithAge.create({ age: 3 }).success(function() { this.UserWithAge.create({ age: 3 }).success(function() {
......
...@@ -444,6 +444,140 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -444,6 +444,140 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}) })
}) })
}) })
before(function(done) {
this.UserEager = this.sequelize.define('UserEagerLoadingSaves', {
username : Helpers.Sequelize.STRING,
age : Helpers.Sequelize.INTEGER
}, { timestamps: false })
this.ProjectEager = this.sequelize.define('ProjectEagerLoadingSaves', {
title : Helpers.Sequelize.STRING,
overdue_days: Helpers.Sequelize.INTEGER
}, { timestamps: false })
this.UserEager.hasMany(this.ProjectEager, { as: 'Projects' })
this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah' })
this.sequelize.sync({ force: true }).success(done)
})
it('saves one object that has a collection of eagerly loaded objects', function(done) {
this.UserEager.create({ username: 'joe', age: 1 }).success(function(user) {
this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }).success(function(project1) {
this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }).success(function(project2) {
user.setProjects([project1, project2]).success(function() {
this.UserEager.find({where: {age: 1}, include: [{model: this.ProjectEager, as: 'Projects'}]}).success(function(user) {
expect(user.username).toEqual('joe')
expect(user.age).toEqual(1)
expect(user.projects).toBeDefined()
expect(user.projects.length).toEqual(2)
user.age = user.age + 1; // happy birthday joe
user.save().success(function(saveduser) {
expect(user.username).toEqual('joe')
expect(user.age).toEqual(2)
expect(user.projects).toBeDefined()
expect(user.projects.length).toEqual(2)
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('saves many objects that each a have collection of eagerly loaded objects', function(done) {
this.UserEager.create({ username: 'bart', age: 20 }).success(function(bart) {
this.UserEager.create({ username: 'lisa', age: 20 }).success(function(lisa) {
this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }).success(function(detention1) {
this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }).success(function(detention2) {
this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }).success(function(exam1) {
this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }).success(function(exam2) {
bart.setProjects([detention1, detention2]).success(function() {
lisa.setProjects([exam1, exam2]).success(function() {
this.UserEager.findAll({where: {age: 20}, order: 'username ASC', include: [{model: this.ProjectEager, as: 'Projects'}]}).success(function(simpsons) {
var _bart, _lisa;
expect(simpsons.length).toEqual(2)
_bart = simpsons[0];
_lisa = simpsons[1];
expect(_bart.projects).toBeDefined()
expect(_lisa.projects).toBeDefined()
expect(_bart.projects.length).toEqual(2)
expect(_lisa.projects.length).toEqual(2)
_bart.age = _bart.age + 1; // happy birthday bart - off to Moe's
_bart.save().success(function(savedbart) {
expect(savedbart.username).toEqual('bart')
expect(savedbart.age).toEqual(21)
_lisa.username = 'lsimpson';
_lisa.save().success(function(savedlisa) {
expect(savedlisa.username).toEqual('lsimpson')
expect(savedlisa.age).toEqual(20)
done()
})
})
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('saves many objects that each has one eagerly loaded object (to which they belong)', function(done) {
this.UserEager.create({ username: 'poobah', age: 18 }).success(function(user) {
this.ProjectEager.create({ title: 'homework', overdue_days: 10 }).success(function(homework) {
this.ProjectEager.create({ title: 'party', overdue_days: 2 }).success(function(party) {
user.setProjects([homework, party]).success(function() {
this.ProjectEager.findAll({include: [{model: this.UserEager, as: 'Poobah'}]}).success(function(projects) {
expect(projects.length).toEqual(2)
expect(projects[0].poobah).toBeDefined()
expect(projects[1].poobah).toBeDefined()
expect(projects[0].poobah.username).toEqual('poobah')
expect(projects[1].poobah.username).toEqual('poobah')
projects[0].title = 'partymore';
projects[1].title = 'partymore';
projects[0].overdue_days = 0;
projects[1].overdue_days = 0;
projects[0].save().success(function() {
projects[1].save().success(function() {
this.ProjectEager.findAll({where: {title: 'partymore', overdue_days: 0}, include: [{model: this.UserEager, as: 'Poobah'}]}).success(function(savedprojects) {
expect(savedprojects.length).toEqual(2)
expect(savedprojects[0].poobah).toBeDefined()
expect(savedprojects[1].poobah).toBeDefined()
expect(savedprojects[0].poobah.username).toEqual('poobah')
expect(savedprojects[1].poobah.username).toEqual('poobah')
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
}) })
describe('toJSON', function toJSON() { describe('toJSON', function toJSON() {
......
if(typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../index")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("Language Util"), function() {
describe("Plural", function(){
before(function(done) {
Helpers.initTests({
dialect: dialect,
onComplete: function(sequelize) {
this.sequelize = sequelize
this.sequelize.options.language = 'es'
done()
}.bind(this)
})
})
it("should rename tables to their plural form...", function(done){
var table = this.sequelize.define('arbol', {name: Sequelize.STRING})
var table2 = this.sequelize.define('androide', {name: Sequelize.STRING})
expect(table.tableName).toEqual('arboles')
expect(table2.tableName).toEqual('androides')
done()
})
it("should be able to pluralize/singularize associations...", function(done){
var table = this.sequelize.define('arbol', {name: Sequelize.STRING})
var table2 = this.sequelize.define('androide', {name: Sequelize.STRING})
var table3 = this.sequelize.define('hombre', {name: Sequelize.STRING})
table.hasOne(table2)
table2.belongsTo(table)
table3.hasMany(table2)
expect(table.associations.androides.identifier).toEqual('arbolId')
expect(table2.associations.arboles).toBeDefined()
expect(table3.associations.androideshombres).toBeDefined()
done()
})
})
})
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!