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

Commit 9a0c7d4b by Jan Aagaard Meier

Merge master

2 parents d7aa81d1 94ec0379
...@@ -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
mariadb: mariadb:
@DIALECT=mariadb make test @DIALECT=mariadb make test
......
...@@ -66,7 +66,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -66,7 +66,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Eager loading of nested associations [#388](https://github.com/sequelize/sequelize/issues/388) - 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
......
...@@ -77,6 +77,8 @@ ...@@ -77,6 +77,8 @@
- [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] 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] 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
......
...@@ -4,27 +4,61 @@ module.exports = (function() { ...@@ -4,27 +4,61 @@ module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) { var HasManyDoubleLinked = function(definition, instance) {
this.__factory = definition this.__factory = definition
this.instance = instance this.instance = instance
// Alias the quoting methods for code brevity
this.QueryInterface = instance.QueryInterface
} }
HasManyDoubleLinked.prototype.injectGetter = function(options) { HasManyDoubleLinked.prototype.injectGetter = function(options) {
var self = this, _options = options var self = this, _options = options
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {}, options = _options || {} var where = {}
, connectorDAO = self.__factory.connectorDAO
, options = _options || {}
, queryOptions = {}
, association = self.__factory.target.associations[self.__factory.associationAccessor]
//fully qualify //fully qualify
var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys) var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys)
, instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id' , instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id'
where[self.__factory.connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey] where[connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey]
var primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes) var primaryKeys = Object.keys(connectorDAO.primaryKeys)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0] , foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
, foreignPrimary = Object.keys(self.__factory.target.primaryKeys) , foreignPrimary = Object.keys(self.__factory.target.primaryKeys)
foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id' foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id'
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary} where[connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary}
if (association.hasJoinTableModel) {
queryOptions.hasJoinTableModel = true
queryOptions.joinTableModel = connectorDAO
if (!options.attributes) {
options.attributes = [
self.QueryInterface.quoteIdentifier(self.__factory.target.tableName)+".*"
]
}
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + elem, true)
)
})
} else {
Utils._.forOwn(connectorDAO.rawAttributes, function (elem, key) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + key, true)
)
})
}
}
if (options.where) { if (options.where) {
if (Array.isArray(options.where)) { if (Array.isArray(options.where)) {
...@@ -44,7 +78,7 @@ module.exports = (function() { ...@@ -44,7 +78,7 @@ module.exports = (function() {
options.where = where; options.where = where;
} }
self.__factory.target.findAllJoin(self.__factory.connectorDAO.tableName, options) self.__factory.target.findAllJoin(connectorDAO.tableName, options, queryOptions)
.on('success', function(objects) { customEventEmitter.emit('success', objects) }) .on('success', function(objects) { customEventEmitter.emit('success', objects) })
.on('error', function(err){ customEventEmitter.emit('error', err) }) .on('error', function(err){ customEventEmitter.emit('error', err) })
.on('sql', function(sql) { customEventEmitter.emit('sql', sql)}) .on('sql', function(sql) { customEventEmitter.emit('sql', sql)})
...@@ -53,26 +87,41 @@ module.exports = (function() { ...@@ -53,26 +87,41 @@ module.exports = (function() {
return customEventEmitter.run() return customEventEmitter.run()
} }
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations) { HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor] , association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys) , sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys) , targetKeys = Object.keys(self.__factory.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, unassociatedObjects;
var obsoleteAssociations = oldAssociations.filter(function (old) { unassociatedObjects = newAssociations.filter(function (obj) {
// Return only those old associations that are not found in new return !Utils._.find(oldAssociations, function (old) {
return !Utils._.find(newAssociations, function (obj) { return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
}) })
var unassociatedObjects = newAssociations.filter(function (obj) { oldAssociations.forEach(function (old) {
// Return only those associations that are new var newObj = Utils._.find(newAssociations, function (obj) {
return !Utils._.find(oldAssociations, function (old) { return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
if (!newObj) {
obsoleteAssociations.push(old)
} else if (association.hasJoinTableModel) {
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, newObj[self.__factory.connectorDAO.name], defaultAttributes)
}
changedAssociation.where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id
changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id
changedAssociations.push(changedAssociation)
}
}) })
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
...@@ -93,12 +142,22 @@ module.exports = (function() { ...@@ -93,12 +142,22 @@ module.exports = (function() {
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
if (association.hasJoinTableModel) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.connectorDAO.name], defaultAttributes)
}
return attributes return attributes
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk)) chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
} }
if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) {
chainer.add(self.__factory.connectorDAO.update(assoc.attributes, assoc.where))
})
}
chainer chainer
.run() .run()
.success(function() { emitterProxy.emit('success', newAssociations) }) .success(function() { emitterProxy.emit('success', newAssociations) })
...@@ -106,7 +165,7 @@ module.exports = (function() { ...@@ -106,7 +165,7 @@ module.exports = (function() {
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .on('sql', function(sql) { emitterProxy.emit('sql', sql) })
} }
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) { HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation, additionalAttributes, exists) {
var attributes = {} var attributes = {}
, association = this.__factory.target.associations[this.__factory.associationAccessor] , association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
...@@ -117,10 +176,21 @@ module.exports = (function() { ...@@ -117,10 +176,21 @@ module.exports = (function() {
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id) attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
this.__factory.connectorDAO.create(attributes) if (exists) { // implies hasJoinTableModel === true
.success(function() { emitterProxy.emit('success', newAssociation) }) var where = attributes
.error(function(err) { emitterProxy.emit('error', err) }) attributes = Utils._.defaults({}, newAssociation[association.connectorDAO.name], additionalAttributes)
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
association.connectorDAO.update(attributes, where).proxy(emitterProxy)
} else {
if (association.hasJoinTableModel === true) {
attributes = Utils._.defaults(attributes, newAssociation[association.connectorDAO.name], additionalAttributes)
}
this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
}
} }
return HasManyDoubleLinked return HasManyDoubleLinked
......
...@@ -13,15 +13,23 @@ module.exports = (function() { ...@@ -13,15 +13,23 @@ module.exports = (function() {
this.options = options this.options = options
this.useJunctionTable = this.options.useJunctionTable === undefined ? true : this.options.useJunctionTable this.useJunctionTable = this.options.useJunctionTable === undefined ? true : this.options.useJunctionTable
this.isSelfAssociation = (this.source.tableName === this.target.tableName) this.isSelfAssociation = (this.source.tableName === this.target.tableName)
this.hasJoinTableModel = !!this.options.joinTableModel
var combinedTableName = Utils.combineTableNames( var combinedTableName;
this.source.tableName, if (this.hasJoinTableModel) {
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName combinedTableName = this.options.joinTableModel.tableName
) } else if (this.options.joinTableName) {
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName) combinedTableName = this.options.joinTableName
this.associationAccessor = this.options.as || this.combinedName } else {
combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
)
}
this.options.useHooks = options.useHooks this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName)
this.options.useHooks = options.useHooks
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))
...@@ -39,6 +47,7 @@ module.exports = (function() { ...@@ -39,6 +47,7 @@ module.exports = (function() {
// or in an extra table which connects two tables // or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() { HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor)
, 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?
...@@ -63,7 +72,21 @@ module.exports = (function() { ...@@ -63,7 +72,21 @@ module.exports = (function() {
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true} combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true} combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options) if (this.hasJoinTableModel === true) {
this.connectorDAO = this.options.joinTableModel
// remove any previously defined PKs
Utils._.each(this.connectorDAO.attributes, function(dataTypeString, attributeName) {
if (dataTypeString.toString().indexOf('PRIMARY KEY') !== -1) {
delete self.connectorDAO.rawAttributes[attributeName]
}
})
this.connectorDAO.rawAttributes = Utils._.merge(this.connectorDAO.rawAttributes, combinedTableAttributes)
this.connectorDAO.init(this.connectorDAO.daoFactoryManager)
} else {
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
}
if (!this.isSelfAssociation) { if (!this.isSelfAssociation) {
this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO
...@@ -94,7 +117,7 @@ module.exports = (function() { ...@@ -94,7 +117,7 @@ module.exports = (function() {
return new Class(self, this).injectGetter(options) return new Class(self, this).injectGetter(options)
} }
obj[this.accessors.hasAll] = function(objects) { obj[this.accessors.hasAll] = function(objects) {
var instance = this; var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]() instance[self.accessors.get]()
...@@ -137,7 +160,7 @@ module.exports = (function() { ...@@ -137,7 +160,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 = []
} }
...@@ -149,7 +172,7 @@ module.exports = (function() { ...@@ -149,7 +172,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)
...@@ -160,20 +183,21 @@ module.exports = (function() { ...@@ -160,20 +183,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);
} }
...@@ -190,9 +214,7 @@ module.exports = (function() { ...@@ -190,9 +214,7 @@ module.exports = (function() {
currentAssociatedObjects.forEach(function(association) { currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) { if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations[newAssociations.length] = association newAssociations.push(association)
} else {
oldAssociations[oldAssociations.length] = association
} }
}) })
......
...@@ -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
} }
} }
......
...@@ -352,14 +352,16 @@ module.exports = (function() { ...@@ -352,14 +352,16 @@ module.exports = (function() {
} }
//right now, the caller (has-many-double-linked) is in charge of the where clause //right now, the caller (has-many-double-linked) is in charge of the where clause
DAOFactory.prototype.findAllJoin = function(joinTableName, options) { DAOFactory.prototype.findAllJoin = function(joinTableName, options, queryOptions) {
var optcpy = Utils._.clone(options) var optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteIdentifier(this.tableName)+".*"] optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteIdentifier(this.tableName)+".*"]
// whereCollection is used for non-primary key updates // whereCollection is used for non-primary key updates
this.options.whereCollection = optcpy.where || null; this.options.whereCollection = optcpy.where || null;
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, { type: 'SELECT' }) return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
}, queryOptions))
} }
/** /**
......
...@@ -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 })
......
...@@ -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) {
...@@ -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')
} }
......
...@@ -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.1.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,7 +46,7 @@ ...@@ -46,7 +46,7 @@
"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",
......
...@@ -580,6 +580,136 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -580,6 +580,136 @@ 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("Foreign key constraints", function() { describe("Foreign key constraints", function() {
......
...@@ -3491,6 +3491,44 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -3491,6 +3491,44 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('should be able to reference a table with a schema set', function(done) {
var self = this
var sequelize = this.sequelize
var UserPub = this.sequelize.define('UserPub', {
username: Sequelize.STRING
}, { schema: 'prefix' })
var ItemPub = this.sequelize.define('ItemPub', {
name: Sequelize.STRING
}, { schema: 'prefix' })
UserPub.hasMany(ItemPub, {
foreignKeyConstraint: true
})
var run = function() {
UserPub.sync({ force: true }).success(function() {
ItemPub.sync({ force: true }).on('sql', function(sql) {
if (dialect === "postgres") {
expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/)
} else {
expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/)
}
done()
})
})
}
if (dialect === "postgres") {
this.sequelize.queryInterface.createSchema('prefix').success(function() {
run.call(self)
})
} else {
run.call(self)
}
})
it("should be able to create and update records under any valid schematic", function(done){ it("should be able to create and update records under any valid schematic", function(done){
var self = this var self = this
...@@ -3533,17 +3571,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -3533,17 +3571,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe('references', function() { describe('references', function() {
this.timeout(3000) this.timeout(3000)
beforeEach(function(done) { beforeEach(function(done) {
this.Author = this.sequelize.define('author', { firstName: Sequelize.STRING }) var self = this
this.Author.sync({ force: true }).success(function() {
done() this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
self.Author = self.sequelize.define('author', { firstName: Sequelize.STRING })
self.Author.sync({ force: true }).success(function() {
done()
})
})
}) })
}) })
afterEach(function(done) { afterEach(function(done) {
var self = this var self = this
self.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() { this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() { self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
done() done()
}) })
...@@ -3567,11 +3612,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -3567,11 +3612,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
Post.sync().on('sql', function(sql) { Post.sync().on('sql', function(sql) {
if (dialect === 'postgres') { if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/) expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/)
} } else if (dialect === 'mysql' || dialect === 'mariadb') {
else if (dialect === 'mysql' || dialect === 'mariadb') {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/) expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/)
} } else if (dialect === 'sqlite') {
else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/) expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/)
} else { } else {
throw new Error('Undefined dialect!') throw new Error('Undefined dialect!')
...@@ -3598,11 +3641,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -3598,11 +3641,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
Post.sync().on('sql', function(sql) { Post.sync().on('sql', function(sql) {
if (dialect === 'postgres') { if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/) expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/)
} } else if (dialect === 'mysql' || dialect === 'mariadb') {
else if (dialect === 'mysql' || dialect === 'mariadb') {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/) expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/)
} } else if (dialect === 'sqlite') {
else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/) expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/)
} else { } else {
throw new Error('Undefined dialect!') throw new Error('Undefined dialect!')
...@@ -3639,15 +3680,12 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -3639,15 +3680,12 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}).error(function(err) { }).error(function(err) {
if (dialect === 'mysql') { if (dialect === 'mysql') {
expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/) expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/)
} } else if (dialect === 'mariadb') {
else if (dialect === 'mariadb') {
expect(err.message).to.match(/Can\'t create table/) expect(err.message).to.match(/Can\'t create table/)
} } else if (dialect === 'sqlite') {
else if (dialect === 'sqlite') {
// the parser should not end up here ... see above // the parser should not end up here ... see above
expect(1).to.equal(2) expect(1).to.equal(2)
} } else if (dialect === 'postgres') {
else if (dialect === 'postgres') {
expect(err.message).to.match(/relation "4uth0r5" does not exist/) expect(err.message).to.match(/relation "4uth0r5" does not exist/)
} else { } else {
throw new Error('Undefined dialect!') throw new Error('Undefined dialect!')
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!