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

Commit 7175103a by Daniel Durante

Merge branch 'master' into forceparanoid

Conflicts:
	lib/dao-factory.js
	lib/dao.js
2 parents 7d96bb1e 1149f3ea
Showing with 1415 additions and 214 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,11 +176,22 @@ module.exports = (function() { ...@@ -117,11 +176,22 @@ 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)
if (exists) { // implies hasJoinTableModel === true
var where = attributes
attributes = Utils._.defaults({}, newAssociation[association.connectorDAO.name], additionalAttributes)
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) this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) }) .success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) }) .error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .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;
if (this.hasJoinTableModel) {
combinedTableName = this.options.joinTableModel.tableName
} else if (this.options.joinTableName) {
combinedTableName = this.options.joinTableName
} else {
combinedTableName = Utils.combineTableNames(
this.source.tableName, this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
) )
}
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName) 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,9 +60,15 @@ module.exports = (function() { ...@@ -48,9 +60,15 @@ 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
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[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] 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
var combinedTableAttributes = {} var combinedTableAttributes = {}
...@@ -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}
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) 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
...@@ -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)
}
})
var tick = 0
var next = function(err, i) {
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) instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) }) .success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) }) .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
} }
......
...@@ -122,25 +122,20 @@ module.exports = (function() { ...@@ -122,25 +122,20 @@ module.exports = (function() {
}) })
} }
var errors = this.validate()
if (!!errors) {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors) self.hookValidate().error(function(err) {
}).run() emitter.emit('error', err)
} }).success(function() {
for (var attrName in self.daoFactory.rawAttributes) {
for (var attrName in this.daoFactory.rawAttributes) { if (self.daoFactory.rawAttributes.hasOwnProperty(attrName)) {
if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) { var definition = self.daoFactory.rawAttributes[attrName]
var definition = this.daoFactory.rawAttributes[attrName]
, isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type , isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type
, hasValue = values[attrName] !== undefined , isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isMySQL = this.daoFactory.daoFactoryManager.sequelize.options.dialect === "mysql" , isMySQL = self.daoFactory.daoFactoryManager.sequelize.options.dialect === "mysql"
, ciCollation = !!this.daoFactory.options.collate && this.daoFactory.options.collate.match(/_ci$/i) , ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
, valueOutOfScope
if (isEnum && isMySQL && ciCollation && hasValue) { // Unfortunately for MySQL CI collation we need to map/lowercase values again
if (isEnum && isMySQL && ciCollation && (attrName in values)) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase()) var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1 valueOutOfScope = scopeIndex === -1
...@@ -148,12 +143,6 @@ module.exports = (function() { ...@@ -148,12 +143,6 @@ module.exports = (function() {
if (!valueOutOfScope) { if (!valueOutOfScope) {
values[attrName] = definition.values[scopeIndex] values[attrName] = definition.values[scopeIndex]
} }
} else {
valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1)
}
if (isEnum && hasValue && valueOutOfScope && !(definition.allowNull === true && values[attrName] === null)) {
throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', '))
} }
if (isHstore) { if (isHstore) {
...@@ -164,26 +153,67 @@ module.exports = (function() { ...@@ -164,26 +153,67 @@ module.exports = (function() {
} }
} }
if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) { if (self.__options.timestamps && self.dataValues.hasOwnProperty(updatedAtAttr)) {
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(this.sequelize.options.dialect) self.dataValues[updatedAtAttr] = values[updatedAtAttr] = (
(
self.isNewRecord
&& !!self.daoFactory.rawAttributes[updatedAtAttr]
&& !!self.daoFactory.rawAttributes[updatedAtAttr].defaultValue
)
? self.daoFactory.rawAttributes[updatedAtAttr].defaultValue
: Utils.now(self.sequelize.options.dialect))
} }
if (this.isNewRecord) { var query = null
this.isDirty = false , args = []
return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values) , hook = ''
if (self.isNewRecord) {
self.isDirty = false
query = 'insert'
args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values]
hook = 'Create'
} else { } else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }; var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }
if (identifier === null && this.__options.whereCollection !== null) { if (identifier === null && self.__options.whereCollection !== null) {
identifier = this.__options.whereCollection; identifier = self.__options.whereCollection;
} }
this.isDirty = false self.isDirty = false
var tableName = this.QueryInterface.QueryGenerator.addSchema(this.__factory) query = 'update'
, query = this.QueryInterface.update(this, tableName, values, identifier, options) args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values, identifier, options]
hook = 'Update'
}
self.__factory.runHooks('before' + hook, values, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
return query // redeclare our new values
args[2] = newValues || args[2]
self.QueryInterface[query].apply(self.QueryInterface, args)
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.emit('error', err)
})
.success(function(result) {
self.__factory.runHooks('after' + hook, result.values, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
} }
result.dataValues = newValues
emitter.emit('success', result)
})
})
})
})
}).run()
} }
/* /*
...@@ -231,6 +261,16 @@ module.exports = (function() { ...@@ -231,6 +261,16 @@ module.exports = (function() {
return (Utils._.isEmpty(errors) ? null : errors) return (Utils._.isEmpty(errors) ? null : errors)
} }
/*
* Validate this dao's attribute values according to validation rules set in the dao definition.
*
* @return CustomEventEmitter with null if validation successful; otherwise an object containing { field name : [error msgs] } entries.
*/
DAO.prototype.hookValidate = function(object) {
var validator = new DaoValidator(this, object)
return validator.hookValidate()
}
DAO.prototype.updateAttributes = function(updates, fields) { DAO.prototype.updateAttributes = function(updates, fields) {
this.setAttributes(updates) this.setAttributes(updates)
...@@ -280,14 +320,41 @@ module.exports = (function() { ...@@ -280,14 +320,41 @@ module.exports = (function() {
options = options || {} options = options || {}
options.force = options.force === undefined ? false : Boolean(options.force) options.force = options.force === undefined ? false : Boolean(options.force)
if (this.__options.timestamps && this.__options.paranoid && options.force === false) { var self = this
var attr = Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored) , query = null
this.dataValues[attr] = new Date()
return this.save() return new Utils.CustomEventEmitter(function(emitter) {
self.daoFactory.runHooks(self.daoFactory.options.hooks.beforeDestroy, self.dataValues, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
if (self.__options.timestamps && self.__options.paranoid && options.force === false) {
var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored)
self.dataValues[attr] = new Date()
query = self.save()
} else { } else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }; var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
return this.QueryInterface.delete(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), identifier) query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.__factory.tableName, self.__factory.options.schema), identifier)
} }
query.on('sql', function(sql) {
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.emit('error', err)
})
.success(function(results) {
self.daoFactory.runHooks(self.daoFactory.options.hooks.afterDestroy, self.dataValues, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', results)
})
})
})
}).run()
} }
DAO.prototype.increment = function(fields, count) { DAO.prototype.increment = function(fields, count) {
...@@ -422,10 +489,15 @@ module.exports = (function() { ...@@ -422,10 +489,15 @@ module.exports = (function() {
} }
if (this.__options.timestamps) { if (this.__options.timestamps) {
if (!this.defaultValues[Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)]) {
defaults[Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)] = Utils.now(this.sequelize.options.dialect) defaults[Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)] = Utils.now(this.sequelize.options.dialect)
}
if (!this.defaultValues[Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)]) {
defaults[Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)] = Utils.now(this.sequelize.options.dialect) defaults[Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)] = Utils.now(this.sequelize.options.dialect)
}
if (this.__options.paranoid) { if (this.__options.paranoid && !this.defaultValues[Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)]) {
defaults[Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)] = null defaults[Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)] = null
} }
} }
......
...@@ -261,20 +261,6 @@ module.exports = (function() { ...@@ -261,20 +261,6 @@ 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
...@@ -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) {
...@@ -325,8 +354,29 @@ module.exports = (function() { ...@@ -325,8 +354,29 @@ module.exports = (function() {
} 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) {
......
...@@ -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) + ';'
} }
} }
......
...@@ -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() {
......
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,19 +122,18 @@ if (dialect.match(/^mysql/)) { ...@@ -122,19 +122,18 @@ 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()
}) })
}) })
}) })
}) })
})
})
describe('primaryKeys', function() { describe('primaryKeys', function() {
it("determines the correct primaryKeys", function(done) { it("determines the correct primaryKeys", function(done) {
......
...@@ -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`"
} }
], ],
......
...@@ -435,14 +435,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -435,14 +435,13 @@ 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' })
}).to.throw(Error, 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished')
done() done()
}) })
}) })
}) })
})
describe('table', function() { describe('table', function() {
[ [
......
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!