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

Commit 7175103a by Daniel Durante

Merge branch 'master' into forceparanoid

Conflicts:
	lib/dao-factory.js
	lib/dao.js
2 parents 7d96bb1e 1149f3ea
Showing with 1331 additions and 204 deletions
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"waitsFor": false, "waitsFor": false,
"runs": false "runs": false
}, },
"node": true,
"camelcase": true, "camelcase": true,
"curly": true, "curly": true,
"forin": true, "forin": true,
...@@ -18,5 +19,7 @@ ...@@ -18,5 +19,7 @@
"asi": true, "asi": true,
"evil": false, "evil": false,
"laxcomma": true, "laxcomma": true,
"es5": true "es5": true,
"quotmark": false,
"strict": false
} }
\ No newline at end of file
...@@ -12,11 +12,12 @@ teaser: ...@@ -12,11 +12,12 @@ teaser:
echo '' echo ''
test: test:
@make teaser && \ @if [ "$$GREP" ]; then \
./node_modules/mocha/bin/mocha \ make teaser && ./node_modules/mocha/bin/mocha --colors --reporter $(REPORTER) -g "$$GREP" $(TESTS); \
--colors \ else \
--reporter $(REPORTER) \ make teaser && ./node_modules/mocha/bin/mocha --colors --reporter $(REPORTER) $(TESTS); \
$(TESTS) fi
sqlite: sqlite:
@DIALECT=sqlite make test @DIALECT=sqlite make test
......
...@@ -33,6 +33,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/ ...@@ -33,6 +33,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
- Promises - Promises
- Hooks/callbacks/lifecycle events
## Documentation and Updates ## ## Documentation and Updates ##
...@@ -62,10 +63,10 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -62,10 +63,10 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Support for update of tables without primary key - Support for update of tables without primary key
- MariaDB support - MariaDB support
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sequelize/sequelize/issues/388)
- ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy)) - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - ~~Move validation of enum attribute value to validate method~~ Implemented in [#894](https://github.com/sequelize/sequelize/pull/894) thanks to @durango
- ~~BLOB~~ [#842](https://github.com/sequelize/sequelize/pull/842), thanks to @janmeier - ~~BLOB~~ [#842](https://github.com/sequelize/sequelize/pull/842), thanks to @janmeier
- ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude - ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
......
...@@ -39,6 +39,9 @@ ...@@ -39,6 +39,9 @@
- [BUG] Added tests & bugfixes for DAO-Factory.update and array of values in where clause [#880](https://github.com/sequelize/sequelize/pull/880). thanks to domasx2 - [BUG] Added tests & bugfixes for DAO-Factory.update and array of values in where clause [#880](https://github.com/sequelize/sequelize/pull/880). thanks to domasx2
- [BUG] sqlite no longer leaks a global `db` variable [#900](https://github.com/sequelize/sequelize/pull/900). thanks to xming - [BUG] sqlite no longer leaks a global `db` variable [#900](https://github.com/sequelize/sequelize/pull/900). thanks to xming
- [BUG] Fix for counts queries with no result [#906](https://github.com/sequelize/sequelize/pull/906). thanks to iamjochem - [BUG] Fix for counts queries with no result [#906](https://github.com/sequelize/sequelize/pull/906). thanks to iamjochem
- [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier
- [BUG] Allow definition of defaultValue for the timestamp columns (createdAt, updatedAt, deletedAt) [#930](https://github.com/sequelize/sequelize/pull/930). Thank to durango
- [BUG] Don't delete foreign keys of many-to-many associations, if still needed. [#961](https://github.com/sequelize/sequelize/pull/961). thanks to sdepold
- [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
...@@ -72,8 +75,11 @@ ...@@ -72,8 +75,11 @@
- [FEATURE] Added Sequelize.fn() and Sequelize.col() to properly call columns and functions within Sequelize. [#882](https://github.com/sequelize/sequelize/pull/882). thanks to janmeier - [FEATURE] Added Sequelize.fn() and Sequelize.col() to properly call columns and functions within Sequelize. [#882](https://github.com/sequelize/sequelize/pull/882). thanks to janmeier
- [FEATURE] Sequelize.import supports relative paths. [#901](https://github.com/sequelize/sequelize/pull/901). thanks to accerqueira. - [FEATURE] Sequelize.import supports relative paths. [#901](https://github.com/sequelize/sequelize/pull/901). thanks to accerqueira.
- [FEATURE] Sequelize.import can now handle functions. [#911](https://github.com/sequelize/sequelize/pull/911). Thanks to davidrivera. - [FEATURE] Sequelize.import can now handle functions. [#911](https://github.com/sequelize/sequelize/pull/911). Thanks to davidrivera.
- [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier
- [FEATURE] Uses sequelize.fn and sequelize.col functionality to allow you to use the value of another column or a function when updating. It also allows you to use a function as a default value when supported (in sqlite and postgres). [#928](https://github.com/sequelize/sequelize/pull/928). thanks to janmeier - [FEATURE] Uses sequelize.fn and sequelize.col functionality to allow you to use the value of another column or a function when updating. It also allows you to use a function as a default value when supported (in sqlite and postgres). [#928](https://github.com/sequelize/sequelize/pull/928). thanks to janmeier
- [FEATURE] Added possibility to pass options to node-mysql. [#929](https://github.com/sequelize/sequelize/pull/929). thanks to poying
- [FEATURE] Triggers for Postgres. [#915](https://github.com/sequelize/sequelize/pull/915). Thanks to jonathana.
- [FEATURE] Support for join tables. [#877](https://github.com/sequelize/sequelize/pull/877). Thanks to janmeier.
- [FEATURE] Support for hooks. [#894](https://github.com/sequelize/sequelize/pull/894). Thanks to durango.
- [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
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier - [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango - [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
...@@ -14,6 +14,8 @@ module.exports = (function() { ...@@ -14,6 +14,8 @@ module.exports = (function() {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.source.options.language) + "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.options.useHooks = options.useHooks
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
...@@ -31,7 +33,7 @@ module.exports = (function() { ...@@ -31,7 +33,7 @@ module.exports = (function() {
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes to DAO proto each time a new assoc is added
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes); this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes)
return this return this
} }
......
...@@ -4,27 +4,61 @@ module.exports = (function() { ...@@ -4,27 +4,61 @@ module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) { var HasManyDoubleLinked = function(definition, instance) {
this.__factory = definition this.__factory = definition
this.instance = instance this.instance = instance
// Alias the quoting methods for code brevity
this.QueryInterface = instance.QueryInterface
} }
HasManyDoubleLinked.prototype.injectGetter = function(options) { HasManyDoubleLinked.prototype.injectGetter = function(options) {
var self = this, _options = options var self = this, _options = options
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {}, options = _options || {} var where = {}
, connectorDAO = self.__factory.connectorDAO
, options = _options || {}
, queryOptions = {}
, association = self.__factory.target.associations[self.__factory.associationAccessor]
//fully qualify //fully qualify
var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys) var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys)
, instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id' , instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id'
where[self.__factory.connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey] where[connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey]
var primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes) var primaryKeys = Object.keys(connectorDAO.primaryKeys)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0] , foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
, foreignPrimary = Object.keys(self.__factory.target.primaryKeys) , foreignPrimary = Object.keys(self.__factory.target.primaryKeys)
foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id' foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id'
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary} where[connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary}
if (association.hasJoinTableModel) {
queryOptions.hasJoinTableModel = true
queryOptions.joinTableModel = connectorDAO
if (!options.attributes) {
options.attributes = [
self.QueryInterface.quoteIdentifier(self.__factory.target.tableName)+".*"
]
}
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + elem, true)
)
})
} else {
Utils._.forOwn(connectorDAO.rawAttributes, function (elem, key) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + key, true)
)
})
}
}
if (options.where) { if (options.where) {
if (Array.isArray(options.where)) { if (Array.isArray(options.where)) {
...@@ -44,7 +78,7 @@ module.exports = (function() { ...@@ -44,7 +78,7 @@ module.exports = (function() {
options.where = where; options.where = where;
} }
self.__factory.target.findAllJoin(self.__factory.connectorDAO.tableName, options) self.__factory.target.findAllJoin(connectorDAO.tableName, options, queryOptions)
.on('success', function(objects) { customEventEmitter.emit('success', objects) }) .on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('error', function(err){ customEventEmitter.emit('error', err) }) .on('error', function(err){ customEventEmitter.emit('error', err) })
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)}) .on('sql', function(sql) { customEventEmitter.emit('sql', sql)})
...@@ -53,26 +87,41 @@ module.exports = (function() { ...@@ -53,26 +87,41 @@ module.exports = (function() {
return customEventEmitter.run() return customEventEmitter.run()
} }
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations) { HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor] , association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys) , sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys) , targetKeys = Object.keys(self.__factory.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, unassociatedObjects;
var obsoleteAssociations = oldAssociations.filter(function (old) { unassociatedObjects = newAssociations.filter(function (obj) {
// Return only those old associations that are not found in new return !Utils._.find(oldAssociations, function (old) {
return !Utils._.find(newAssociations, function (obj) { return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
}) })
var unassociatedObjects = newAssociations.filter(function (obj) { oldAssociations.forEach(function (old) {
// Return only those associations that are new var newObj = Utils._.find(newAssociations, function (obj) {
return !Utils._.find(oldAssociations, function (old) { return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
if (!newObj) {
obsoleteAssociations.push(old)
} else if (association.hasJoinTableModel) {
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, newObj[self.__factory.connectorDAO.name], defaultAttributes)
}
changedAssociation.where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id
changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id
changedAssociations.push(changedAssociation)
}
}) })
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
...@@ -93,12 +142,22 @@ module.exports = (function() { ...@@ -93,12 +142,22 @@ module.exports = (function() {
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
if (association.hasJoinTableModel) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.connectorDAO.name], defaultAttributes)
}
return attributes return attributes
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk)) chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
} }
if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) {
chainer.add(self.__factory.connectorDAO.update(assoc.attributes, assoc.where))
})
}
chainer chainer
.run() .run()
.success(function() { emitterProxy.emit('success', newAssociations) }) .success(function() { emitterProxy.emit('success', newAssociations) })
...@@ -106,7 +165,7 @@ module.exports = (function() { ...@@ -106,7 +165,7 @@ module.exports = (function() {
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .on('sql', function(sql) { emitterProxy.emit('sql', sql) })
} }
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) { HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) {
var attributes = {} var attributes = {}
, association = this.__factory.target.associations[this.__factory.associationAccessor] , association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
...@@ -117,10 +176,21 @@ module.exports = (function() { ...@@ -117,10 +176,21 @@ module.exports = (function() {
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id) attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
this.__factory.connectorDAO.create(attributes) if (exists) { // implies hasJoinTableModel === true
.success(function() { emitterProxy.emit('success', newAssociation) }) var where = attributes
.error(function(err) { emitterProxy.emit('error', err) }) attributes = Utils._.defaults({}, newAssociation[association.connectorDAO.name], additionalAttributes)
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
association.connectorDAO.update(attributes, where).proxy(emitterProxy)
} else {
if (association.hasJoinTableModel === true) {
attributes = Utils._.defaults(attributes, newAssociation[association.connectorDAO.name], additionalAttributes)
}
this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
}
} }
return HasManyDoubleLinked return HasManyDoubleLinked
......
...@@ -30,7 +30,7 @@ module.exports = (function() { ...@@ -30,7 +30,7 @@ module.exports = (function() {
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {}).daoFactory.primaryKeys || {}) , associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {})
, associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id' , associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
......
...@@ -13,12 +13,22 @@ module.exports = (function() { ...@@ -13,12 +13,22 @@ module.exports = (function() {
this.options = options this.options = options
this.useJunctionTable = this.options.useJunctionTable === undefined ? true : this.options.useJunctionTable this.useJunctionTable = this.options.useJunctionTable === undefined ? true : this.options.useJunctionTable
this.isSelfAssociation = (this.source.tableName === this.target.tableName) this.isSelfAssociation = (this.source.tableName === this.target.tableName)
this.hasJoinTableModel = !!this.options.joinTableModel
var combinedTableName = Utils.combineTableNames( var combinedTableName;
this.source.tableName, if (this.hasJoinTableModel) {
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName combinedTableName = this.options.joinTableModel.tableName
) } else if (this.options.joinTableName) {
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName) combinedTableName = this.options.joinTableName
} else {
combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
)
}
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName)
this.options.useHooks = options.useHooks
this.associationAccessor = this.options.as || this.combinedName this.associationAccessor = this.options.as || this.combinedName
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language)) var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
...@@ -37,6 +47,8 @@ module.exports = (function() { ...@@ -37,6 +47,8 @@ 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)
, self = this
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "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?
...@@ -48,8 +60,14 @@ module.exports = (function() { ...@@ -48,8 +60,14 @@ module.exports = (function() {
} else { } else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
delete this.source.rawAttributes[this.foreignIdentifier]
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier] if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier]
}
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) {
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
}
} }
// define a new model, which connects the models // define a new model, which connects the models
...@@ -61,7 +79,21 @@ module.exports = (function() { ...@@ -61,7 +79,21 @@ module.exports = (function() {
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true} combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true} combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options) if (this.hasJoinTableModel === true) {
this.connectorDAO = this.options.joinTableModel
// remove any previously defined PKs
Utils._.each(this.connectorDAO.attributes, function(dataTypeString, attributeName) {
if (dataTypeString.toString().indexOf('PRIMARY KEY') !== -1) {
delete self.connectorDAO.rawAttributes[attributeName]
}
})
this.connectorDAO.rawAttributes = Utils._.merge(this.connectorDAO.rawAttributes, combinedTableAttributes)
this.connectorDAO.init(this.connectorDAO.daoFactoryManager)
} else {
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
}
if (!this.isSelfAssociation) { if (!this.isSelfAssociation) {
this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO
...@@ -92,7 +124,7 @@ module.exports = (function() { ...@@ -92,7 +124,7 @@ module.exports = (function() {
return new Class(self, this).injectGetter(options) return new Class(self, this).injectGetter(options)
} }
obj[this.accessors.hasAll] = function(objects) { obj[this.accessors.hasAll] = function(objects) {
var instance = this; var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]() instance[self.accessors.get]()
...@@ -135,7 +167,7 @@ module.exports = (function() { ...@@ -135,7 +167,7 @@ module.exports = (function() {
HasMany.prototype.injectSetter = function(obj) { HasMany.prototype.injectSetter = function(obj) {
var self = this var self = this
obj[this.accessors.set] = function(newAssociatedObjects) { obj[this.accessors.set] = function(newAssociatedObjects, defaultAttributes) {
if (newAssociatedObjects === null) { if (newAssociatedObjects === null) {
newAssociatedObjects = [] newAssociatedObjects = []
} }
...@@ -147,7 +179,7 @@ module.exports = (function() { ...@@ -147,7 +179,7 @@ module.exports = (function() {
instance[self.accessors.get]() instance[self.accessors.get]()
.success(function(oldAssociatedObjects) { .success(function(oldAssociatedObjects) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects) new Class(self, instance).injectSetter(emitter, oldAssociatedObjects, newAssociatedObjects, defaultAttributes)
}) })
.error(function(err) { .error(function(err) {
emitter.emit('error', err) emitter.emit('error', err)
...@@ -158,20 +190,21 @@ module.exports = (function() { ...@@ -158,20 +190,21 @@ module.exports = (function() {
}).run() }).run()
} }
obj[this.accessors.add] = function(newAssociatedObject) { obj[this.accessors.add] = function(newAssociatedObject, additionalAttributes) {
var instance = this var instance = this
, primaryKeys = Object.keys(newAssociatedObject.daoFactory.primaryKeys || {}) , primaryKeys = Object.keys(newAssociatedObject.daoFactory.primaryKeys || {})
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id' , primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, where = {} , where = {}
where[newAssociatedObject.daoFactory.tableName+'.'+primaryKey] = newAssociatedObject[primaryKey] where[newAssociatedObject.daoFactory.tableName+'.'+primaryKey] = newAssociatedObject[primaryKey]
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({ where: where }) instance[self.accessors.get]({ where: where })
.error(function(err){ emitter.emit('error', err)}) .error(function(err){ emitter.emit('error', err)})
.success(function(currentAssociatedObjects) { .success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0) { if (currentAssociatedObjects.length === 0 || self.hasJoinTableModel === true) {
var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked var Class = self.connectorDAO ? HasManyMultiLinked : HasManySingleLinked
new Class(self, instance).injectAdder(emitter, newAssociatedObject) new Class(self, instance).injectAdder(emitter, newAssociatedObject, additionalAttributes, !!currentAssociatedObjects.length)
} else { } else {
emitter.emit('success', newAssociatedObject); emitter.emit('success', newAssociatedObject);
} }
...@@ -184,15 +217,44 @@ module.exports = (function() { ...@@ -184,15 +217,44 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().success(function(currentAssociatedObjects) { instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = [] var newAssociations = []
, oldAssociations = []
currentAssociatedObjects.forEach(function(association) { currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations.push(association) newAssociations.push(association)
}
}) })
instance[self.accessors.set](newAssociations) var tick = 0
.success(function() { customEventEmitter.emit('success', null) }) var next = function(err, i) {
.error(function(err) { customEventEmitter.emit('error', err) }) if (!!err || i >= oldAssociations.length) {
return run(err)
}
oldAssociations[i].destroy().error(function(err) {
next(err)
})
.success(function() {
tick++
next(null, tick)
})
}
var run = function(err) {
if (!!err) {
return customEventEmitter.emit('error', err)
}
instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) })
}
if (oldAssociations.length > 0) {
next(null, tick)
} else {
run()
}
}) })
}) })
return customEventEmitter.run() return customEventEmitter.run()
...@@ -201,5 +263,27 @@ module.exports = (function() { ...@@ -201,5 +263,27 @@ module.exports = (function() {
return this return this
} }
/**
* The method checks if it is ok to delete the previously defined foreign key.
* This is done because we need to keep the foreign key if another association
* is depending on it.
*
* @param {DaoFactory} daoFactory The source or target DaoFactory of this assocation
* @param {[type]} identifier The name of the foreign key identifier
* @return {Boolean} Whether or not the deletion of the foreign key is ok.
*/
var isForeignKeyDeletionAllowedFor = function(daoFactory, identifier) {
var isAllowed = true
, associationNames = Utils._.without(Object.keys(daoFactory.associations), this.associationAccessor)
associationNames.forEach(function(associationName) {
if (daoFactory.associations[associationName].identifier === identifier) {
isAllowed = false
}
})
return isAllowed
}
return HasMany return HasMany
})() })()
...@@ -18,6 +18,8 @@ module.exports = (function() { ...@@ -18,6 +18,8 @@ module.exports = (function() {
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
this.options.useHooks = options.useHooks
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))), 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, this.target.options.language))) set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
......
...@@ -13,10 +13,21 @@ module.exports = { ...@@ -13,10 +13,21 @@ module.exports = {
return source.rawAttributes[key].primaryKey return source.rawAttributes[key].primaryKey
}) })
if(primaryKeys.length == 1) { if (primaryKeys.length === 1) {
newAttribute.references = source.tableName, if (!!source.options.schema) {
newAttribute.references = source.daoFactoryManager.sequelize.queryInterface.QueryGenerator.addSchema({
tableName: source.tableName,
options: {
schema: source.options.schema,
schemaDelimiter: source.options.schemaDelimiter
}
})
} else {
newAttribute.references = source.tableName
}
newAttribute.referencesKey = primaryKeys[0] newAttribute.referencesKey = primaryKeys[0]
newAttribute.onDelete = options.onDelete, newAttribute.onDelete = options.onDelete
newAttribute.onUpdate = options.onUpdate newAttribute.onUpdate = options.onUpdate
} }
} }
......
...@@ -7,6 +7,11 @@ var Utils = require("./../utils") ...@@ -7,6 +7,11 @@ var Utils = require("./../utils")
var Mixin = module.exports = function(){} var Mixin = module.exports = function(){}
Mixin.hasOne = function(associatedDAO, options) { Mixin.hasOne = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table // the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
...@@ -18,8 +23,13 @@ Mixin.hasOne = function(associatedDAO, options) { ...@@ -18,8 +23,13 @@ Mixin.hasOne = function(associatedDAO, options) {
} }
Mixin.belongsTo = function(associatedDAO, options) { Mixin.belongsTo = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in this table // the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend((options || {}), this.options)) var association = new BelongsTo(this, associatedDAO, Utils._.extend(options, this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
...@@ -29,6 +39,11 @@ Mixin.belongsTo = function(associatedDAO, options) { ...@@ -29,6 +39,11 @@ Mixin.belongsTo = function(associatedDAO, options) {
} }
Mixin.hasMany = function(associatedDAO, options) { Mixin.hasMany = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table or in a connecting table // the id is in the foreign table or in a connecting table
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
......
...@@ -18,19 +18,50 @@ DaoValidator.prototype.validate = function() { ...@@ -18,19 +18,50 @@ DaoValidator.prototype.validate = function() {
return errors return errors
} }
DaoValidator.prototype.hookValidate = function() {
var self = this
, errors = {}
return new Utils.CustomEventEmitter(function(emitter) {
self.model.daoFactory.runHooks('beforeValidate', self.model.dataValues, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
self.model.dataValues = newValues || self.model.dataValues
errors = Utils._.extend(errors, validateAttributes.call(self))
errors = Utils._.extend(errors, validateModel.call(self))
if (Object.keys(errors).length > 0) {
return emitter.emit('error', errors)
}
self.model.daoFactory.runHooks('afterValidate', self.model.dataValues, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
self.model.dataValues = newValues || self.model.dataValues
emitter.emit('success', self.model)
})
})
}).run()
}
// private // private
var validateModel = function() { var validateModel = function() {
var errors = {} var self = this
, errors = {}
// for each model validator for this DAO // for each model validator for this DAO
Utils._.each(this.model.__options.validate, function(validator, validatorType) { Utils._.each(this.model.__options.validate, function(validator, validatorType) {
try { try {
validator.apply(this.model) validator.apply(self.model)
} catch (err) { } catch (err) {
errors[validatorType] = [err.message] // TODO: data structure needs to change for 2.0 errors[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
} }
}.bind(this)) })
return errors return errors
} }
...@@ -54,11 +85,12 @@ var validateAttributes = function() { ...@@ -54,11 +85,12 @@ var validateAttributes = function() {
} }
var validateAttribute = function(value, field) { var validateAttribute = function(value, field) {
var errors = {} var self = this
, errors = {}
// for each validator // for each validator
Utils._.each(this.model.validators[field], function(details, validatorType) { Utils._.each(this.model.validators[field], function(details, validatorType) {
var validator = prepareValidationOfAttribute.call(this, value, details, validatorType) var validator = prepareValidationOfAttribute.call(self, value, details, validatorType)
try { try {
validator.fn.apply(null, validator.args) validator.fn.apply(null, validator.args)
...@@ -74,7 +106,7 @@ var validateAttribute = function(value, field) { ...@@ -74,7 +106,7 @@ var validateAttribute = function(value, field) {
errors[field] = errors[field] || [] errors[field] = errors[field] || []
errors[field].push(msg) errors[field].push(msg)
} }
}.bind(this)) // for each validator for this field })
return errors return errors
} }
......
...@@ -261,27 +261,13 @@ module.exports = (function() { ...@@ -261,27 +261,13 @@ module.exports = (function() {
}, },
/* /*
Globally enable foreign key constraints
*/
enableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('enableForeignKeyConstraintsQuery')
},
/*
Globally disable foreign key constraints
*/
disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting Arrays: First argument should be qouted, second argument should be append without quoting
Objects: Objects:
* If raw is set, that value should be returned verbatim, without quoting * If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by * If fn is set, the string should start with the value of fn, starting paren, followed by
the values of cols (which is assumed to be an array), quoted and joined with ', ', the values of cols (which is assumed to be an array), quoted and joined with ', ',
unless they are themselves objects unless they are themselves objects
* If direction is set, should be prepended * If direction is set, should be prepended
...@@ -303,6 +289,49 @@ module.exports = (function() { ...@@ -303,6 +289,49 @@ module.exports = (function() {
}, },
/* /*
Create a trigger
*/
createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams,
optionsArray) {
throwMethodUndefined('createTrigger')
},
/*
Drop a trigger
*/
dropTrigger: function(tableName, triggerName) {
throwMethodUndefined('dropTrigger')
},
/*
Rename a trigger
*/
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
throwMethodUndefined('renameTrigger')
},
/*
Create a function
*/
createFunction: function(functionName, params, returnType, language, body, options) {
throwMethodUndefined('createFunction')
},
/*
Drop a function
*/
dropFunction: function(functionName, params) {
throwMethodUndefined('dropFunction')
},
/*
Rename a function
*/
renameFunction: function(oldFunctionName, params, newFunctionName) {
throwMethodUndefined('renameFunction')
},
/*
Escape an identifier (e.g. a table or attribute name) Escape an identifier (e.g. a table or attribute name)
*/ */
quoteIdentifier: function(identifier, force) { quoteIdentifier: function(identifier, force) {
...@@ -323,10 +352,31 @@ module.exports = (function() { ...@@ -323,10 +352,31 @@ module.exports = (function() {
if (value instanceof Utils.fn || value instanceof Utils.col) { if (value instanceof Utils.fn || value instanceof Utils.col) {
return value.toString(this) return value.toString(this)
} else { } else {
return SqlString.escape(value, false, null, this.dialect, field) return SqlString.escape(value, false, null, this.dialect, field)
} }
} },
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
throwMethodUndefined('getForeignKeysQuery')
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
throwMethodUndefined('dropForeignKeyQuery')
}
} }
var throwMethodUndefined = function(methodName) { var throwMethodUndefined = function(methodName) {
......
...@@ -260,6 +260,21 @@ module.exports = (function() { ...@@ -260,6 +260,21 @@ module.exports = (function() {
result = result.map(Dot.transform) result = result.map(Dot.transform)
} else if (this.options.hasJoin === true) { } else if (this.options.hasJoin === true) {
result = transformRowsWithEagerLoadingIntoDaos.call(this, results) result = transformRowsWithEagerLoadingIntoDaos.call(this, results)
} else if (this.options.hasJoinTableModel === true) {
result = results.map(function(result) {
result = Dot.transform(result)
var joinTableData = result[this.options.joinTableModel.name]
, joinTableDAO = this.options.joinTableModel.build(joinTableData, { isNewRecord: false, isDirty: false })
, mainDao
delete result[this.options.joinTableModel.name]
mainDao = this.callee.build(result, { isNewRecord: false, isDirty: false })
mainDao[this.options.joinTableModel.name] = joinTableDAO
return mainDao
}.bind(this))
} else { } else {
result = results.map(function(result) { result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false, isDirty: false }) return this.callee.build(result, { isNewRecord: false, isDirty: false })
......
...@@ -253,17 +253,25 @@ module.exports = (function() { ...@@ -253,17 +253,25 @@ 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 emitter = new (require('events').EventEmitter)()
var connectionConfig = {
host: config.host, host: config.host,
port: config.port, port: config.port,
user: config.username, user: config.username,
password: config.password, password: config.password,
database: config.database, database: config.database,
timezone: 'Z' timezone: 'Z'
}) };
if (config.dialectOptions) {
Object.keys(config.dialectOptions).forEach(function (key) {
connectionConfig[key] = config.dialectOptions[key];
});
}
var connection = mysql.createConnection(connectionConfig);
connection.connect(function(err) { connection.connect(function(err) {
if (err) { if (err) {
...@@ -386,4 +394,4 @@ module.exports = (function() { ...@@ -386,4 +394,4 @@ module.exports = (function() {
} }
return ConnectorManager return ConnectorManager
})() })()
\ No newline at end of file
...@@ -404,10 +404,10 @@ module.exports = (function() { ...@@ -404,10 +404,10 @@ module.exports = (function() {
}, },
showIndexQuery: function(tableName, options) { showIndexQuery: function(tableName, options) {
var sql = "SHOW INDEX FROM <%= tableName %><%= options %>" var sql = "SHOW INDEX FROM `<%= tableName %>`<%= options %>"
return Utils._.template(sql)({ return Utils._.template(sql)({
tableName: tableName, tableName: tableName,
options: (options || {}).database ? ' FROM ' + options.database : '' options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
}) })
}, },
...@@ -589,22 +589,34 @@ module.exports = (function() { ...@@ -589,22 +589,34 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() {
var sql = "SET FOREIGN_KEY_CHECKS = 1;"
return Utils._.template(sql, {})
},
disableForeignKeyConstraintsQuery: function() {
var sql = "SET FOREIGN_KEY_CHECKS = 0;"
return Utils._.template(sql, {})
},
quoteIdentifier: function(identifier, force) { quoteIdentifier: function(identifier, force) {
return Utils.addTicks(identifier, "`") return Utils.addTicks(identifier, "`")
}, },
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.') return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return "SELECT CONSTRAINT_NAME as constraint_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '" + tableName + "' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='" + schemaName + "' AND REFERENCED_TABLE_NAME IS NOT NULL;"
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';'
} }
} }
......
...@@ -145,6 +145,7 @@ module.exports = (function() { ...@@ -145,6 +145,7 @@ module.exports = (function() {
// Closes a client correctly even if we have backed up queries // Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346 // https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client)) this.client.on('drain', this.client.end.bind(this.client))
this.client = null
} }
this.isConnecting = false this.isConnecting = false
......
...@@ -15,7 +15,7 @@ module.exports = (function() { ...@@ -15,7 +15,7 @@ module.exports = (function() {
var schema = (!!opts.options && !!opts.options.schema ? opts.options.schema : undefined) var schema = (!!opts.options && !!opts.options.schema ? opts.options.schema : undefined)
var schemaDelimiter = (!!opts.options && !!opts.options.schemaDelimiter ? opts.options.schemaDelimiter : undefined) var schemaDelimiter = (!!opts.options && !!opts.options.schemaDelimiter ? opts.options.schemaDelimiter : undefined)
if (!!opts.tableName) { if (!!opts && !!opts.tableName) {
tableName = opts.tableName tableName = opts.tableName
} }
else if (typeof opts === "string") { else if (typeof opts === "string") {
...@@ -26,7 +26,7 @@ module.exports = (function() { ...@@ -26,7 +26,7 @@ module.exports = (function() {
return tableName return tableName
} }
return this.quoteIdentifier(schema) + '.' + this.quoteIdentifier(tableName) return this.quoteIdentifiers((!!schema ? (schema + '.' + tableName) : tableName));
}, },
createSchema: function(schema) { createSchema: function(schema) {
...@@ -177,7 +177,7 @@ module.exports = (function() { ...@@ -177,7 +177,7 @@ module.exports = (function() {
if (definition.indexOf('DEFAULT') > 0) { if (definition.indexOf('DEFAULT') > 0) {
attrSql += Utils._.template(query)({ attrSql += Utils._.template(query)({
tableName: this.quoteIdentifiers(tableName), tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' SET DEFAULT' + definition.match(/DEFAULT ([^;]+)/)[1] query: this.quoteIdentifier(attributeName) + ' SET DEFAULT ' + definition.match(/DEFAULT ([^;]+)/)[1]
}) })
definition = definition.replace(/(DEFAULT[^;]+)/, '').trim() definition = definition.replace(/(DEFAULT[^;]+)/, '').trim()
...@@ -665,10 +665,10 @@ module.exports = (function() { ...@@ -665,10 +665,10 @@ module.exports = (function() {
if(dataType.references) { if(dataType.references) {
template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)" template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)"
replacements.referencesTable = this.quoteIdentifier(dataType.references) replacements.referencesTable = this.quoteIdentifiers(dataType.references)
if(dataType.referencesKey) { if(dataType.referencesKey) {
replacements.referencesKey = this.quoteIdentifier(dataType.referencesKey) replacements.referencesKey = this.quoteIdentifiers(dataType.referencesKey)
} else { } else {
replacements.referencesKey = this.quoteIdentifier('id') replacements.referencesKey = this.quoteIdentifier('id')
} }
...@@ -714,12 +714,79 @@ module.exports = (function() { ...@@ -714,12 +714,79 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() { createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
return false // not supported by dialect var sql = [
'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
, '<%= eventType %> <%= eventSpec %>'
, 'ON <%= tableName %>'
, '<%= optionsSpec %>'
, 'EXECUTE PROCEDURE <%= functionName %>(<%= paramList %>);'
].join('\n\t')
return Utils._.template(sql)({
constraintVal: this.triggerEventTypeIsConstraint(eventType),
triggerName: triggerName,
eventType: this.decodeTriggerEventType(eventType),
eventSpec: this.expandTriggerEventSpec(fireOnSpec),
tableName: tableName,
optionsSpec: this.expandOptions(optionsArray),
functionName: functionName,
paramList: this.expandFunctionParamList(functionParams)
})
},
dropTrigger: function(tableName, triggerName) {
var sql = 'DROP TRIGGER <%= triggerName %> ON <%= tableName %> RESTRICT;'
return Utils._.template(sql)({
triggerName: triggerName,
tableName: tableName
})
}, },
disableForeignKeyConstraintsQuery: function() { renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
return false // not supported by dialect var sql = 'ALTER TRIGGER <%= oldTriggerName %> ON <%= tableName %> RENAME TO <%= newTriggerName%>;'
return Utils._.template(sql)({
tableName: tableName,
oldTriggerName: oldTriggerName,
newTriggerName: newTriggerName
})
},
createFunction: function(functionName, params, returnType, language, body, options) {
var sql = [ "CREATE FUNCTION <%= functionName %>(<%= paramList %>)"
, "RETURNS <%= returnType %> AS $$"
, "BEGIN"
, "\t<%= body %>"
, "END;"
, "$$ language '<%= language %>'<%= options %>;"
].join('\n')
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params),
returnType: returnType,
body: body.replace('\n', '\n\t'),
language: language,
options: this.expandOptions(options)
})
},
dropFunction: function(functionName, params) {
// RESTRICT is (currently, as of 9.2) default but we'll be explicit
var sql = 'DROP FUNCTION <%= functionName %>(<%= paramList %>) RESTRICT;'
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params)
})
},
renameFunction: function(oldFunctionName, params, newFunctionName) {
var sql = 'ALTER FUNCTION <%= oldFunctionName %>(<%= paramList %>) RENAME TO <%= newFunctionName %>;'
return Utils._.template(sql)({
oldFunctionName: oldFunctionName,
paramList: this.expandFunctionParamList(params),
newFunctionName: newFunctionName
})
}, },
databaseConnectionUri: function(config) { databaseConnectionUri: function(config) {
...@@ -739,6 +806,77 @@ module.exports = (function() { ...@@ -739,6 +806,77 @@ module.exports = (function() {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'")) return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"))
}, },
expandFunctionParamList: function expandFunctionParamList(params) {
if (Utils._.isUndefined(params) || !Utils._.isArray(params)) {
throw new Error("expandFunctionParamList: function parameters array required, including an empty one for no arguments")
}
var paramList = Utils._.each(params, function expandParam(curParam){
paramDef = []
if (Utils._.has(curParam, 'type')) {
if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam.direction) }
if (Utils._.has(curParam, 'name')) { paramDef.push(curParam.name) }
paramDef.push(curParam.type)
} else {
throw new Error('createFunction called with a parameter with no type')
}
return paramDef.join(' ')
})
return paramList.join(', ')
},
expandOptions: function expandOptions(options) {
return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
'' : '\n\t' + options.join('\n\t')
},
decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) {
var EVENT_DECODER = {
'after': 'AFTER',
'before': 'BEFORE',
'instead_of': 'INSTEAD OF',
'after_constraint': 'AFTER'
}
if (!Utils._.has(EVENT_DECODER, eventSpecifier)) {
throw new Error('Invalid trigger event specified: ' + eventSpecifier)
}
return EVENT_DECODER[eventSpecifier]
},
triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) {
return eventSpecifier === 'after_constrain' ? 'CONSTRAINT ' : ''
},
expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) {
if (Utils._.isEmpty(fireOnSpec)) {
throw new Error('no table change events specified to trigger on')
}
return Utils._.map(fireOnSpec, function parseTriggerEventSpec(fireValue, fireKey){
var EVENT_MAP = {
'insert': 'INSERT',
'update': 'UPDATE',
'delete': 'DELETE',
'truncate': 'TRUNCATE'
}
if (!Utils._.has(EVENT_MAP, fireKey)) {
throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey)
}
var eventSpec = EVENT_MAP[fireKey]
if (eventSpec === 'UPDATE') {
if (Utils._.isArray(fireValue) && fireValue.length > 0) {
eventSpec += ' OF ' + fireValue.join(', ')
}
}
return eventSpec
}).join(' OR ')
},
pgListEnums: function(tableName, attrName, options) { pgListEnums: function(tableName, attrName, options) {
if (arguments.length === 1) { if (arguments.length === 1) {
options = tableName options = tableName
...@@ -858,6 +996,28 @@ module.exports = (function() { ...@@ -858,6 +996,28 @@ module.exports = (function() {
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers: function(identifiers, force) {
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('.')
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return "SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '" + tableName + "' LIMIT 1) AND r.contype = 'f' ORDER BY 1;"
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';'
} }
} }
......
...@@ -179,7 +179,7 @@ module.exports = (function() { ...@@ -179,7 +179,7 @@ module.exports = (function() {
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0]) , attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as) , tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
, attrRight = include.association.identifier , attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else { } else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys) var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
...@@ -377,16 +377,6 @@ module.exports = (function() { ...@@ -377,16 +377,6 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() {
var sql = "PRAGMA foreign_keys = ON;"
return Utils._.template(sql, {})
},
disableForeignKeyConstraintsQuery: function() {
var sql = "PRAGMA foreign_keys = OFF;"
return Utils._.template(sql, {})
},
hashToWhereConditions: function(hash) { hashToWhereConditions: function(hash) {
for (var key in hash) { for (var key in hash) {
if (hash.hasOwnProperty(key)) { if (hash.hasOwnProperty(key)) {
...@@ -486,6 +476,17 @@ module.exports = (function() { ...@@ -486,6 +476,17 @@ module.exports = (function() {
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.') return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return "PRAGMA foreign_key_list(\"" + tableName + "\")"
} }
} }
......
...@@ -118,5 +118,60 @@ var QueryInterface = module.exports = { ...@@ -118,5 +118,60 @@ var QueryInterface = module.exports = {
queryAndEmit.call(this, queries.splice(queries.length - 1)[0], methodName, {}, emitter) queryAndEmit.call(this, queries.splice(queries.length - 1)[0], methodName, {}, emitter)
} }
}.bind(this)) }.bind(this))
},
dropAllTables: function() {
var self = this
return new Utils.CustomEventEmitter(function(dropAllTablesEmitter) {
var events = []
, chainer = new Utils.QueryChainer()
, onError = function(err) {
self.emit('dropAllTables', err)
dropAllTablesEmitter.emit('error', err)
}
self
.showAllTables()
.error(onError)
.success(function(tableNames) {
self
.sequelize
.query('PRAGMA foreign_keys;')
.proxy(dropAllTablesEmitter, { events: ['sql'] })
.error(onError)
.success(function(result) {
var foreignKeysAreEnabled = result.foreign_keys === 1
if (foreignKeysAreEnabled) {
var queries = []
queries.push('PRAGMA foreign_keys = OFF')
tableNames.forEach(function(tableName) {
queries.push(self.QueryGenerator.dropTableQuery(tableName).replace(';', ''))
})
queries.push('PRAGMA foreign_keys = ON')
QueryInterface.execMultiQuery.call(self, queries, 'dropAllTables', dropAllTablesEmitter, self.queryAndEmit)
} else {
// add the table removal query to the chainer
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [ tableName, { cascade: true } ])
})
chainer
.runSerially()
.proxy(dropAllTablesEmitter, { events: ['sql'] })
.error(onError)
.success(function() {
self.emit('dropAllTables', null)
dropAllTablesEmitter.emit('success', null)
})
}
})
})
}).run()
} }
} }
...@@ -139,6 +139,10 @@ module.exports = (function() { ...@@ -139,6 +139,10 @@ module.exports = (function() {
result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, "") result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, "")
} }
}) })
} else if (this.sql.indexOf('PRAGMA foreign_keys;') !== -1) {
result = results[0]
} else if (this.sql.indexOf('PRAGMA foreign_keys') !== -1) {
result = results
} }
this.emit('success', result) this.emit('success', result)
......
...@@ -2,26 +2,25 @@ var util = require("util") ...@@ -2,26 +2,25 @@ var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("promise") , Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
, Utils = require('../utils')
var bindToProcess = function(fct) { var bindToProcess = function(fct) {
if (fct) { if (fct && process.domain) {
if(process.domain) { return process.domain.bind(fct)
return process.domain.bind(fct);
}
} }
return fct; return fct
}; };
module.exports = (function() { module.exports = (function() {
var CustomEventEmitter = function(fct) { var CustomEventEmitter = function(fct) {
this.fct = bindToProcess(fct); this.fct = bindToProcess(fct)
} }
util.inherits(CustomEventEmitter, EventEmitter) util.inherits(CustomEventEmitter, EventEmitter)
CustomEventEmitter.prototype.run = function() { CustomEventEmitter.prototype.run = function() {
process.nextTick(function() { tick(function() {
if (this.fct) { if (this.fct) {
this.fct.call(this, this) this.fct.call(this, this)
} }
...@@ -54,25 +53,40 @@ module.exports = (function() { ...@@ -54,25 +53,40 @@ module.exports = (function() {
return this return this
} }
CustomEventEmitter.prototype.sql = CustomEventEmitter.prototype.sql = function(fct) {
function(fct) {
this.on('sql', bindToProcess(fct)) this.on('sql', bindToProcess(fct))
return this; return this;
} }
CustomEventEmitter.prototype.proxy = function(emitter) { /**
proxyEventKeys.forEach(function (eventKey) { * Proxy every event of this custom event emitter to another one.
*
* @param {CustomEventEmitter} emitter The event emitter that should receive the events.
* @return {void}
*/
CustomEventEmitter.prototype.proxy = function(emitter, options) {
options = Utils._.extend({
events: proxyEventKeys,
skipEvents: []
}, options || {})
options.events = Utils._.difference(options.events, options.skipEvents)
options.events.forEach(function (eventKey) {
this.on(eventKey, function (result) { this.on(eventKey, function (result) {
emitter.emit(eventKey, result) emitter.emit(eventKey, result)
}) })
}.bind(this)) }.bind(this))
return this
} }
CustomEventEmitter.prototype.then = CustomEventEmitter.prototype.then = function(onFulfilled, onRejected) {
function (onFulfilled, onRejected) {
var self = this var self = this
onFulfilled = bindToProcess(onFulfilled) onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected) onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
self.on('error', reject) self.on('error', reject)
.on('success', resolve); .on('success', resolve);
......
var Hooks = module.exports = function(){}
Hooks.runHooks = function() {
var self = this
, tick = 0
, hooks = arguments[0]
, args = Array.prototype.slice.call(arguments, 1, arguments.length-1)
, fn = arguments[arguments.length-1]
if (typeof hooks === "string") {
hooks = this.options.hooks[hooks] || []
}
if (!Array.isArray(hooks)) {
hooks = hooks === undefined ? [] : [hooks]
}
if (hooks.length < 1) {
return fn.apply(this, [null].concat(args))
}
var run = function(hook) {
if (!hook) {
return fn.apply(this, [null].concat(args))
}
if (typeof hook === "object") {
hook = hook.fn
}
hook.apply(self, args.concat(function() {
tick++
if (!!arguments[0]) {
return fn(arguments[0])
}
// daoValues = newValues
return run(hooks[tick])
}))
}
run(hooks[tick])
}
Hooks.hook = function(hookType, name, fn) {
// For aliases, we may want to incorporate some sort of way to mitigate this
if (hookType === "beforeDelete") {
hookType = 'beforeDestroy'
}
else if (hookType === "afterDelete") {
hookType = 'afterDestroy'
}
Hooks.addHook.call(this, hookType, name, fn)
}
Hooks.addHook = function(hookType, name, fn) {
if (typeof name === "function") {
fn = name
name = null
}
var method = function() {
fn.apply(this, Array.prototype.slice.call(arguments, 0, arguments.length-1).concat(arguments[arguments.length-1]))
}
// Just in case if we override the default DAOFactory.options
this.options.hooks[hookType] = this.options.hooks[hookType] || []
this.options.hooks[hookType][this.options.hooks[hookType].length] = !!name ? {name: name, fn: method} : method
}
Hooks.beforeValidate = function(name, fn) {
Hooks.addHook.call(this, 'beforeValidate', name, fn)
}
Hooks.afterValidate = function(name, fn) {
Hooks.addHook.call(this, 'afterValidate', name, fn)
}
Hooks.beforeCreate = function(name, fn) {
Hooks.addHook.call(this, 'beforeCreate', name, fn)
}
Hooks.afterCreate = function(name, fn) {
Hooks.addHook.call(this, 'afterCreate', name, fn)
}
Hooks.beforeDestroy = function(name, fn) {
Hooks.addHook.call(this, 'beforeDestroy', name, fn)
}
Hooks.afterDestroy = function(name, fn) {
Hooks.addHook.call(this, 'afterDestroy', name, fn)
}
Hooks.beforeDelete = function(name, fn) {
Hooks.addHook.call(this, 'beforeDestroy', name, fn)
}
Hooks.afterDelete = function(name, fn) {
Hooks.addHook.call(this, 'afterDestroy', name, fn)
}
Hooks.beforeUpdate = function(name, fn) {
Hooks.addHook.call(this, 'beforeUpdate', name, fn)
}
Hooks.afterUpdate = function(name, fn) {
Hooks.addHook.call(this, 'afterUpdate', name, fn)
}
Hooks.beforeBulkCreate = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkCreate', name, fn)
}
Hooks.afterBulkCreate = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkCreate', name, fn)
}
Hooks.beforeBulkDestroy = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkDestroy', name, fn)
}
Hooks.afterBulkDestroy = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkDestroy', name, fn)
}
Hooks.beforeBulkUpdate = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkUpdate', name, fn)
}
Hooks.afterBulkUpdate = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkUpdate', name, fn)
}
...@@ -103,7 +103,8 @@ module.exports = (function() { ...@@ -103,7 +103,8 @@ module.exports = (function() {
native : this.options.native, native : this.options.native,
replication: this.options.replication, replication: this.options.replication,
dialectModulePath: this.options.dialectModulePath, dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries maxConcurrentQueries: this.options.maxConcurrentQueries,
dialectOptions: this.options.dialectOptions,
} }
try { try {
...@@ -160,17 +161,8 @@ module.exports = (function() { ...@@ -160,17 +161,8 @@ module.exports = (function() {
Sequelize.prototype.define = function(daoName, attributes, options) { Sequelize.prototype.define = function(daoName, attributes, options) {
options = options || {} options = options || {}
var globalOptions = this.options var self = this
, globalOptions = this.options
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(dataType, name){
if (Utils.isHash(dataType)) {
dataType = dataType.type
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field '+ name)
}
})
if (globalOptions.define) { if (globalOptions.define) {
options = Utils._.extend({}, globalOptions.define, options) options = Utils._.extend({}, globalOptions.define, options)
...@@ -185,6 +177,40 @@ module.exports = (function() { ...@@ -185,6 +177,40 @@ module.exports = (function() {
options.omitNull = globalOptions.omitNull options.omitNull = globalOptions.omitNull
options.language = globalOptions.language options.language = globalOptions.language
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(dataType, name) {
if (Utils.isHash(dataType)) {
dataType = dataType.type
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field '+ name)
}
if (dataType.toString() === "ENUM") {
attributes[name].validate = attributes[name].validate || {
_checkEnum: function(value) {
var hasValue = value !== undefined
, isMySQL = self.options.dialect === "mysql"
, ciCollation = !!options.collate && options.collate.match(/_ci$/i) !== null
, valueOutOfScope
if (isMySQL && ciCollation && hasValue) {
var scopeIndex = (attributes[name].values || []).map(function(d) { return d.toLowerCase() }).indexOf(value.toLowerCase())
valueOutOfScope = scopeIndex === -1
} else {
valueOutOfScope = ((attributes[name].values || []).indexOf(value) === -1)
}
if (hasValue && valueOutOfScope && !(attributes[name].allowNull === true && values[attrName] === null)) {
throw new Error('Value "' + value + '" for ENUM ' + name + ' is out of allowed scope. Allowed values: ' + attributes[name].values.join(', '))
}
}
}
}
})
// 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)) {
this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName)) this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName))
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"url": "https://github.com/sequelize/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"lodash": "~2.0.0", "lodash": "~2.2.0",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "~1.5.0", "validator": "~1.5.0",
...@@ -46,15 +46,15 @@ ...@@ -46,15 +46,15 @@
"toposort-class": "~0.2.0", "toposort-class": "~0.2.0",
"generic-pool": "2.0.4", "generic-pool": "2.0.4",
"promise": "~3.2.0", "promise": "~3.2.0",
"sql": "~0.26.0" "sql": "~0.28.0"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8", "mysql": "~2.0.0-alpha9",
"pg": "~2.6.0", "pg": "~2.6.0",
"watchr": "~2.4.3", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
"chai": "~1.7.2", "chai": "~1.8.0",
"mocha": "~1.13.0", "mocha": "~1.13.0",
"chai-datetime": "~1.1.1", "chai-datetime": "~1.1.1",
"sinon": "~1.7.3" "sinon": "~1.7.3"
......
module.exports = {
up: function(migration, DataTypes, done) {
migration.createFunction('get_an_answer', [], 'int', 'plpgsql',
'RETURN 42;'
).complete(done);
},
down: function(migration, DataTypes, done) {
migration.dropFunction('get_an_answer', []).complete(done);
}
}
module.exports = {
up: function(migration, DataTypes, done) {
migration.renameFunction('get_an_answer', [], 'get_the_answer').complete(done);
},
down: function(migration, DataTypes, done) {
migration.renameFunction('get_the_answer', [], 'get_an_answer').complete(done);
}
}
module.exports = {
up: function(migration, DataTypes, done) {
migration.dropFunction('get_the_answer', []).complete(done);
},
down: function(migration, DataTypes, done) {
migration.createFunction('get_the_answer', 'int', 'plpgsql',
'RETURN 42;'
).complete(done);
}
}
module.exports = {
up: function(migration, DataTypes, done) {
migration
.createTable('trigger_test', {
name: DataTypes.STRING,
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
allowNull: false
}
})
.complete(done)
},
down: function(migration, DataTypes, done) {
migration.dropTable('trigger_test').complete(done)
}
}
module.exports = {
up: function(migration, DataTypes, done) {
migration.createTrigger('trigger_test', 'updated_at', 'before', {update: true},
'bump_updated_at', []).complete(done);
},
down: function(migration, DataTypes, done) {
migration.dropTrigger('trigger_test', 'updated_at').complete(done);
}
}
module.exports = {
up: function(migration, DataTypes, done) {
migration.renameTrigger('trigger_test', 'updated_at', 'update_updated_at').complete(done);
},
down: function(migration, DataTypes, done) {
migration.renameTrigger('trigger_test', 'update_updated_at', 'updated_at').complete(done);
}
}
module.exports = {
up: function(migration, DataTypes, done) {
migration.dropTrigger('trigger_test', 'update_updated_at').complete(done);
},
down: function(migration, DataTypes, done) {
migration.createTrigger('trigger_test', 'update_updated_at', 'before', {update: true},
'bump_updated_at', []).complete(done);
}
}
...@@ -28,12 +28,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -28,12 +28,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
describe('hasSingle', function() { describe('hasSingle', function() {
beforeEach(function(done) { beforeEach(function(done) {
var self = this var self = this
this.Article = this.sequelize.define('Article', {
'title': DataTypes.STRING this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING })
}) this.Label = this.sequelize.define('Label', { 'text': DataTypes.STRING })
this.Label = this.sequelize.define('Label', {
'text': DataTypes.STRING
})
this.Article.hasMany(this.Label) this.Article.hasMany(this.Label)
...@@ -580,6 +577,201 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -580,6 +577,201 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
done() done()
}) })
}) })
describe('join table model', function () {
beforeEach(function (done) {
this.User = this.sequelize.define('User', {})
this.Project = this.sequelize.define('Project', {})
this.UserProjects = this.sequelize.define('UserProjects', {
status: DataTypes.STRING,
data: DataTypes.INTEGER
})
this.User.hasMany(this.Project, { joinTableModel: this.UserProjects })
this.Project.hasMany(this.User, { joinTableModel: this.UserProjects })
this.sequelize.sync().success(function() { done() })
})
describe('fetching from join table', function () {
it('should contain the data from the join table on .UserProjects a DAO', function (done) {
var self = this
self.User.create().success(function (u) {
self.Project.create().success(function (p) {
u.addProject(p, { status: 'active', data: 42 }).success(function() {
u.getProjects().success(function(projects) {
var project = projects[0]
expect(project.UserProjects).to.be.defined
expect(project.status).not.to.exist
expect(project.UserProjects.status).to.equal('active')
expect(project.UserProjects.data).to.equal(42)
done()
})
})
})
})
})
it('should be able to limit the join table attributes returned', function (done) {
var self = this
self.User.create().success(function (u) {
self.Project.create().success(function (p) {
u.addProject(p, { status: 'active', data: 42 }).success(function() {
u.getProjects({ joinTableAttributes: ['status']}).success(function(projects) {
var project = projects[0]
expect(project.UserProjects).to.be.defined
expect(project.status).not.to.exist
expect(project.UserProjects.status).to.equal('active')
expect(project.UserProjects.data).not.to.exist
done()
})
})
})
})
})
})
describe('inserting in join table', function () {
describe('add', function () {
it('should insert data provided on the object into the join table', function (done) {
var self = this
self.User.create().success(function (u) {
self.Project.create().success(function (p) {
p.UserProjects = {
status: 'active'
}
u.addProject(p).success(function() {
self.UserProjects.find({ where: { UserId: u.id, ProjectId: p.id }}).success(function (up) {
expect(up.status).to.equal('active')
done()
})
})
})
})
})
it('should insert data provided as a second argument into the join table', function (done) {
var self = this
self.User.create().success(function (u) {
self.Project.create().success(function (p) {
u.addProject(p, { status: 'active' }).success(function() {
self.UserProjects.find({ where: { UserId: u.id, ProjectId: p.id }}).success(function (up) {
expect(up.status).to.equal('active')
done()
})
})
})
})
})
})
describe('set', function () {
it('should be able to combine properties on the associated objects, and default values', function (done) {
var self = this
, _done = _.after(2, done)
self.User.create().success(function (u) {
self.Project.bulkCreate([{}, {}]).success(function () {
self.Project.findAll().success(function (projects) {
var p1 = projects[0]
, p2 = projects[1]
p1.UserProjects = {
status: 'inactive'
}
u.setProjects([p1, p2], { status: 'active' }).success(function() {
self.UserProjects.find({ where: { UserId: u.id, ProjectId: p1.id }}).success(function (up) {
expect(up.status).to.equal('inactive')
_done()
})
self.UserProjects.find({ where: { UserId: u.id, ProjectId: p2.id }}).success(function (up) {
expect(up.status).to.equal('active')
_done()
})
})
})
})
})
})
})
})
})
describe('belongsTo and hasMany at once', function() {
beforeEach(function() {
this.A = this.sequelize.define('a', { name: Sequelize.STRING })
this.B = this.sequelize.define('b', { name: Sequelize.STRING })
})
describe('source belongs to target', function() {
beforeEach(function(done) {
this.A.belongsTo(this.B, { as: 'relation1' })
this.A.hasMany(this.B, { as: 'relation2' })
this.B.hasMany(this.A, { as: 'relation2' })
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
it('correctly uses bId in A', function(done) {
var self = this
var a1 = this.A.build({ name: 'a1' })
, b1 = this.B.build({ name: 'b1' })
a1
.save()
.then(function() { return b1.save() })
.then(function() { return a1.setRelation1(b1) })
.then(function() { return self.A.find({ where: { name: 'a1' } }) })
.done(function(a) {
expect(a.bId).to.be.eq(b1.id)
done()
})
})
})
describe('target belongs to source', function() {
beforeEach(function(done) {
this.B.belongsTo(this.A, { as: 'relation1' })
this.A.hasMany(this.B, { as: 'relation2' })
this.B.hasMany(this.A, { as: 'relation2' })
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
it('correctly uses bId in A', function(done) {
var self = this
var a1 = this.A.build({ name: 'a1' })
, b1 = this.B.build({ name: 'b1' })
a1
.save()
.then(function() { return b1.save() })
.then(function() { return b1.setRelation1(a1) })
.then(function() { return self.B.find({ where: { name: 'b1' } }) })
.done(function(b) {
expect(b.aId).to.be.eq(a1.id)
done()
})
})
})
})
}) })
describe("Foreign key constraints", function() { describe("Foreign key constraints", function() {
...@@ -746,4 +938,4 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -746,4 +938,4 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
}) })
\ No newline at end of file
module.exports = { module.exports = {
username: "root", username: process.env.SEQ_USER || "root",
password: null, password: process.env.SEQ_PW || null,
database: 'sequelize_test', database: process.env.SEQ_DB || 'sequelize_test',
host: '127.0.0.1', host: process.env.SEQ_HOST || '127.0.0.1',
pool: { maxConnections: 5, maxIdleTime: 30000}, pool: {
maxConnections: process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_POOL_IDLE || 30000
},
rand: function() { rand: function() {
return parseInt(Math.random() * 999, 10) return parseInt(Math.random() * 999, 10)
...@@ -11,21 +14,29 @@ module.exports = { ...@@ -11,21 +14,29 @@ module.exports = {
//make maxIdleTime small so that tests exit promptly //make maxIdleTime small so that tests exit promptly
mysql: { mysql: {
username: "root", database: process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test',
password: null, username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || "root",
database: 'sequelize_test', password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null,
host: '127.0.0.1', host: process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: 3306, port: process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306,
pool: { maxConnections: 5, maxIdleTime: 30} pool: {
maxConnections: process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 30
}
}, },
sqlite: { sqlite: {
}, },
postgres: { postgres: {
database: 'sequelize_test', database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test',
username: "postgres", username: process.env.SEQ_PG_USER || process.env.SEQ_USER || "postgres",
port: 5432, password: process.env.SEQ_PG_PW || process.env.SEQ_PW || null,
pool: { maxConnections: 5, maxIdleTime: 3000} host: process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432,
pool: {
maxConnections: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
}
} }
} }
...@@ -101,14 +101,23 @@ describe(Support.getTestDialectTeaser("Configuration"), function() { ...@@ -101,14 +101,23 @@ describe(Support.getTestDialectTeaser("Configuration"), function() {
}) })
it('should accept four parameters (database, username, password, options)', function(done) { it('should accept four parameters (database, username, password, options)', function(done) {
var sequelize = new Sequelize('dbname', 'root', 'pass', { port: 999 }) var sequelize = new Sequelize('dbname', 'root', 'pass', {
port: 999,
dialectOptions: {
supportBigNumbers: true,
bigNumberStrings: true
}
})
var config = sequelize.config var config = sequelize.config
expect(config.database).to.equal('dbname') expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root') expect(config.username).to.equal('root')
expect(config.password).to.equal('pass') expect(config.password).to.equal('pass')
expect(config.port).to.equal(999) expect(config.port).to.equal(999)
expect(config.dialectOptions.supportBigNumbers).to.be.true
expect(config.dialectOptions.bigNumberStrings).to.be.true
done() done()
}) })
}) })
}) })
This diff could not be displayed because it is too large.
...@@ -14,7 +14,7 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -14,7 +14,7 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
logging: function(){} logging: function(){}
}, options || {}) }, options || {})
// this.sequelize.options.logging = console.log //this.sequelize.options.logging = console.log
var migrator = new Migrator(this.sequelize, options) var migrator = new Migrator(this.sequelize, options)
migrator migrator
...@@ -28,7 +28,7 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -28,7 +28,7 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
describe('getUndoneMigrations', function() { describe('getUndoneMigrations', function() {
it("returns no files if timestamps are after the files timestamp", function(done) { it("returns no files if timestamps are after the files timestamp", function(done) {
this.init({ from: 20120101010101 }, function(migrator) { this.init({ from: 20140101010101 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) { migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null expect(err).to.be.null
expect(migrations.length).to.equal(0) expect(migrations.length).to.equal(0)
...@@ -86,7 +86,7 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -86,7 +86,7 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
SequelizeMeta.create({ from: null, to: 20111117063700 }).success(function() { SequelizeMeta.create({ from: null, to: 20111117063700 }).success(function() {
migrator.getUndoneMigrations(function(err, migrations) { migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null expect(err).to.be.null
expect(migrations).to.have.length(7) expect(migrations).to.have.length(14)
expect(migrations[0].filename).to.equal('20111130161100-emptyMigration.js') expect(migrations[0].filename).to.equal('20111130161100-emptyMigration.js')
done() done()
}) })
...@@ -286,7 +286,8 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -286,7 +286,8 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
}) })
}) })
}) })
})
})
describe('renameColumn', function() { describe('renameColumn', function() {
it("renames the signature column from user to sig", function(done) { it("renames the signature column from user to sig", function(done) {
...@@ -307,5 +308,145 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -307,5 +308,145 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
}) })
}) })
}) })
})
if (dialect.match(/^postgres/)) {
describe('function migrations', function() {
var generateFunctionCountQuery = function generateFunctionCountQuery(functionName, langName) {
return [
'SELECT * FROM pg_proc p LEFT OUTER JOIN pg_language l ON (l.oid = p.prolang)',
'WHERE p.proname = \'' + functionName + '\' AND l.lanname = \'' + langName + '\';'
].join('\n')
}
var FUNC_NAME = 'get_an_answer'
var RENAME_FUNC_NAME = 'get_the_answer'
// Set up the table and trigger
before(function(done){
this.init({ from: 20130909174103, to: 20130909174103}, function(migrator) {
migrator.migrate().success(function(){
done()
})
})
})
it("creates a function " + FUNC_NAME + "()", function(done) {
this.sequelize.query(generateFunctionCountQuery(FUNC_NAME, 'plpgsql')).success(function(rows){
expect(rows.length).to.equal(1)
done()
})
})
it("renames a function " + FUNC_NAME + "() to " + RENAME_FUNC_NAME + "()", function(done) {
var self = this
this.init({ from: 20130909174253, to: 20130909174253 }, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.query(generateFunctionCountQuery(FUNC_NAME, 'plpgsql')).success(function(rows){
expect(rows.length).to.equal(0)
self.sequelize.query(generateFunctionCountQuery(RENAME_FUNC_NAME, 'plpgsql')).success(function(rows){
expect(rows.length).to.equal(1)
done()
})
})
})
})
})
it("deletes a function " + RENAME_FUNC_NAME + "()", function(done) {
var self = this
this.init({ from: 20130909175000, to: 20130909175000 }, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.query(generateFunctionCountQuery(RENAME_FUNC_NAME, 'plpgsql')).success(function(rows){
expect(rows.length).to.equal(0)
done()
})
})
})
})
})
describe('test trigger migrations', function() {
var generateTriggerCountQuery = function generateTriggerCountQuery(triggerName) {
return 'SELECT * FROM pg_trigger where tgname = \'' + triggerName + '\''
}
var generateTableCountQuery = function generateTableCountQuery(functionName, schemaName) {
return 'SELECT * FROM pg_tables where tablename = \'' + functionName + '\' and schemaname = \'' + schemaName + '\''
}
var TRIGGER_NAME = 'updated_at'
var RENAME_TRIGGER_NAME = 'update_updated_at'
var TABLE_NAME = 'trigger_test'
var CATALOG_NAME = 'public'
// Make sure the function is present
before(function(done){
this.sequelize.query("CREATE FUNCTION bump_updated_at()\n" +
"RETURNS TRIGGER AS $$\n" +
"BEGIN\n" +
"NEW.updated_at = now();\n" +
"RETURN NEW;\n" +
"END;\n" +
"$$ language 'plpgsql';"
).success(function() {done()})
})
// Clean up the function
after(function(done){
this.sequelize.query("DROP FUNCTION IF EXISTS bump_updated_at()").success(function(){ done(); })
})
it("creates a trigger updated_at on trigger_test", function(done) {
var self = this
this.init({ from: 20130909175939, to: 20130909180846}, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.query(generateTableCountQuery(TABLE_NAME, CATALOG_NAME)).success(function(rows){
expect(rows.length).to.equal(1)
self.sequelize.query(generateTriggerCountQuery(TRIGGER_NAME)).success(function(rows){
expect(rows.length).to.equal(1)
done()
})
})
})
})
})
it("renames a trigger on " + TABLE_NAME + " from " + TRIGGER_NAME + " to " + RENAME_TRIGGER_NAME, function(done){
var self = this
this.init({ from: 20130909175939, to: 20130909181148}, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.query(generateTableCountQuery(TABLE_NAME, CATALOG_NAME)).success(function(rows){
expect(rows.length).to.equal(1)
self.sequelize.query(generateTriggerCountQuery(RENAME_TRIGGER_NAME)).success(function(rows){
expect(rows.length).to.equal(1)
self.sequelize.query(generateTriggerCountQuery(TRIGGER_NAME)).success(function(rows){
expect(rows.length).to.equal(0)
done()
})
})
})
})
})
})
it("deletes a trigger " + TRIGGER_NAME + " on trigger_test", function(done) {
var self = this
this.init({ from: 20130909175939, to: 20130909185621}, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.query(generateTriggerCountQuery(TRIGGER_NAME)).success(function(rows){
expect(rows.length).to.equal(0)
migrator.migrate({method: 'down'}).success(function(){
self.sequelize.query(generateTableCountQuery(TABLE_NAME, CATALOG_NAME)).success(function(rows){
expect(rows.length).to.equal(0)
done()
})
})
})
})
})
})
})
} // if dialect postgres
})
...@@ -122,15 +122,14 @@ if (dialect.match(/^mysql/)) { ...@@ -122,15 +122,14 @@ if (dialect.match(/^mysql/)) {
}) })
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
expect(function() { User.create({mood: 'happy'}).error(function(err) {
User.create({mood: 'happy'}) expect(err).to.deep.equal({ mood: [ 'Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver' ] })
}).to.throw(Error, 'Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver')
expect(function() {
var u = User.build({mood: 'SAD'}) var u = User.build({mood: 'SAD'})
u.save() u.save().error(function(err) {
}).to.throw(Error, 'Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver') expect(err).to.deep.equal({ mood: [ 'Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver' ] })
done() done()
})
})
}) })
}) })
}) })
......
...@@ -182,8 +182,8 @@ if (dialect.match(/^mysql/)) { ...@@ -182,8 +182,8 @@ if (dialect.match(/^mysql/)) {
}, { }, {
title: 'functions can take functions as arguments', title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) { arguments: ['myTable', function (sequelize) {
return { return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']] order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
} }
}], }],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;", expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;",
...@@ -194,7 +194,7 @@ if (dialect.match(/^mysql/)) { ...@@ -194,7 +194,7 @@ if (dialect.match(/^mysql/)) {
arguments: ['myTable', function (sequelize) { arguments: ['myTable', function (sequelize) {
return { return {
order: [ order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'] [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
] ]
} }
...@@ -450,10 +450,10 @@ if (dialect.match(/^mysql/)) { ...@@ -450,10 +450,10 @@ if (dialect.match(/^mysql/)) {
showIndexQuery: [ showIndexQuery: [
{ {
arguments: ['User'], arguments: ['User'],
expectation: 'SHOW INDEX FROM User' expectation: 'SHOW INDEX FROM `User`'
}, { }, {
arguments: ['User', { database: 'sequelize' }], arguments: ['User', { database: 'sequelize' }],
expectation: "SHOW INDEX FROM User FROM sequelize" expectation: "SHOW INDEX FROM `User` FROM `sequelize`"
} }
], ],
...@@ -504,7 +504,7 @@ if (dialect.match(/^mysql/)) { ...@@ -504,7 +504,7 @@ if (dialect.match(/^mysql/)) {
describe(suiteTitle, function() { describe(suiteTitle, function() {
tests.forEach(function(test) { tests.forEach(function(test) {
var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments) var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function(done) { it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
if (test.needsSequelize) { if (test.needsSequelize) {
......
...@@ -435,11 +435,10 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -435,11 +435,10 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
it("doesn't save an instance if value is not in the range of enums", function(done) { it("doesn't save an instance if value is not in the range of enums", function(done) {
var self = this this.Review.create({status: 'fnord'}).error(function(err) {
expect(function() { expect(err).to.deep.equal({ status: [ 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished' ] })
self.Review.create({ status: 'fnord' }) done()
}).to.throw(Error, 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished') })
done()
}) })
}) })
}) })
......
var fs = require('fs') var fs = require('fs')
, Sequelize = require(__dirname + "/../index") , Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types") , DataTypes = require(__dirname + "/../lib/data-types")
, config = require(__dirname + "/config/config") , Config = require(__dirname + "/config/config")
var Support = { var Support = {
Sequelize: Sequelize, Sequelize: Sequelize,
...@@ -26,16 +26,19 @@ var Support = { ...@@ -26,16 +26,19 @@ var Support = {
createSequelizeInstance: function(options) { createSequelizeInstance: function(options) {
options = options || {} options = options || {}
options.dialect = options.dialect || 'mysql' options.dialect = options.dialect || 'mysql'
var config = Config[options.dialect]
options.logging = (options.hasOwnProperty('logging') ? options.logging : false) options.logging = (options.hasOwnProperty('logging') ? options.logging : false)
options.pool = options.pool || config.pool options.pool = options.pool || config.pool
var sequelizeOptions = { var sequelizeOptions = {
logging: options.logging, logging: options.logging,
dialect: options.dialect, dialect: options.dialect,
port: options.port || process.env.SEQ_PORT || config[options.dialect].port, port: options.port || process.env.SEQ_PORT || config.port,
pool: options.pool pool: options.pool,
dialectOptions: options.dialectOptions || {}
} }
if (!!options.host) { if (!!options.host) {
...@@ -50,16 +53,11 @@ var Support = { ...@@ -50,16 +53,11 @@ var Support = {
sequelizeOptions.native = true sequelizeOptions.native = true
} }
return this.getSequelizeInstance( return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions)
process.env.SEQ_DB || config[options.dialect].database,
process.env.SEQ_USER || process.env.SEQ_USERNAME || config[options.dialect].username,
process.env.SEQ_PW || process.env.SEQ_PASSWORD || config[options.dialect].password,
sequelizeOptions
)
}, },
getSequelizeInstance: function(db, user, pass, options) { getSequelizeInstance: function(db, user, pass, options) {
options = options || {}; options = options || {}
options.dialect = options.dialect || this.getTestDialect() options.dialect = options.dialect || this.getTestDialect()
return new Sequelize(db, user, pass, options) return new Sequelize(db, user, pass, options)
}, },
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!