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

Commit 3cfce60e by Sascha Depold

Merge branch 'master' into milestones/2.0.0

2 parents ff97ab70 96874904
...@@ -58,6 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -58,6 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 1.7.0 ### 1.7.0
- ~~Check if lodash is a proper alternative to current underscore usage.~~ - ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions - Transactions
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- 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
......
...@@ -22,9 +22,11 @@ module.exports = (function() { ...@@ -22,9 +22,11 @@ module.exports = (function() {
// the id is in the source table // the id is in the source table
BelongsTo.prototype.injectAttributes = function() { BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
, targetKeys = Object.keys(this.target.primaryKeys)
, keyType = ((this.target.hasPrimaryKeys && targetKeys.length === 1) ? this.target.rawAttributes[targetKeys[0]].type : DataTypes.INTEGER)
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName, this.target.options.language) + "Id", this.source.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName, this.target.options.language) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
......
...@@ -57,30 +57,32 @@ module.exports = (function() { ...@@ -57,30 +57,32 @@ module.exports = (function() {
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)
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
var obsoleteAssociations = oldAssociations.filter(function (old) { var obsoleteAssociations = oldAssociations.filter(function (old) {
// Return only those old associations that are not found in new // Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) { 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)
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { var unassociatedObjects = newAssociations.filter(function (obj) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id) // Return only those associations that are new
}) return !Utils._.find(oldAssociations, function (old) {
}) return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
})
})
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function (associatedObject) { var foreignIds = obsoleteAssociations.map(function (associatedObject) {
return associatedObject.id return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id)
}) })
, primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
var where = {} var where = {}
where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignKey] = foreignIds where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where)) chainer.add(self.__factory.connectorDAO.destroy(where))
} }
...@@ -88,8 +90,8 @@ module.exports = (function() { ...@@ -88,8 +90,8 @@ module.exports = (function() {
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) { var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = unassociatedObject[foreignIdentifier] || unassociatedObject.id attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
return attributes return attributes
}) })
...@@ -109,8 +111,11 @@ module.exports = (function() { ...@@ -109,8 +111,11 @@ module.exports = (function() {
, 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;
attributes[this.__factory.identifier] = this.instance[this.__factory.identifier] || this.instance.id var sourceKeys = Object.keys(this.__factory.source.primaryKeys);
attributes[foreignIdentifier] = newAssociation[foreignIdentifier] || newAssociation.id var targetKeys = Object.keys(this.__factory.target.primaryKeys);
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
this.__factory.connectorDAO.create(attributes) this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) }) .success(function() { emitterProxy.emit('success', newAssociation) })
......
...@@ -54,9 +54,12 @@ module.exports = (function() { ...@@ -54,9 +54,12 @@ module.exports = (function() {
// define a new model, which connects the models // define a new model, which connects the models
var combinedTableAttributes = {} var combinedTableAttributes = {}
var keyType = this.options.keyType || DataTypes.INTEGER var sourceKeys = Object.keys(this.source.primaryKeys);
combinedTableAttributes[this.identifier] = {type: keyType, primaryKey: true} var sourceKeyType = ((!this.source.hasPrimaryKeys || sourceKeys.length !== 1) ? DataTypes.INTEGER : this.source.rawAttributes[sourceKeys[0]].type)
combinedTableAttributes[this.foreignIdentifier] = {type: keyType, primaryKey: true} var targetKeys = Object.keys(this.target.primaryKeys);
var targetKeyType = ((!this.target.hasPrimaryKeys || targetKeys.length !== 1) ? DataTypes.INTEGER : this.target.rawAttributes[targetKeys[0]].type)
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options) this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
......
...@@ -27,9 +27,11 @@ module.exports = (function() { ...@@ -27,9 +27,11 @@ module.exports = (function() {
// the id is in the target table // the id is in the target table
HasOne.prototype.injectAttributes = function() { HasOne.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
, sourceKeys = Object.keys(this.source.primaryKeys)
, keyType = ((this.source.hasPrimaryKeys && sourceKeys.length === 1) ? this.source.rawAttributes[sourceKeys[0]].type : DataTypes.INTEGER)
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)
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
......
...@@ -198,15 +198,18 @@ module.exports = (function() { ...@@ -198,15 +198,18 @@ module.exports = (function() {
} }
if (options.force) { if (options.force) {
self.drop().success(doQuery).error(function(err) { emitter.emit('error', err) }) self.drop(options).success(doQuery).error(function(err) { emitter.emit('error', err) })
} else { } else {
doQuery() doQuery()
} }
}).run() }).run()
} }
DAOFactory.prototype.drop = function() { DAOFactory.prototype.drop = function(options) {
return this.QueryInterface.dropTable(this.getTableName(this.tableName)) // Only Postgres' QueryGenerator.dropTableQuery() will add schema manually
var isPostgres = this.options.dialect === "postgres" || (!!this.daoFactoryManager && this.daoFactoryManager.sequelize.options.dialect === "postgres")
, tableName = isPostgres ? this.tableName : this.getTableName()
return this.QueryInterface.dropTable(tableName, options)
} }
DAOFactory.prototype.dropSchema = function(schema) { DAOFactory.prototype.dropSchema = function(schema) {
......
...@@ -188,26 +188,31 @@ module.exports = (function() { ...@@ -188,26 +188,31 @@ module.exports = (function() {
optAttributes = optAttributes.concat(attributes) optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) { if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
var as = include.as , tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName) , attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
var attrLeft = 'id' , tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as) , attrRight = include.association.identifier
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else { } else {
var table = include.daoFactory.tableName var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
var as = include.as , tableSource = tableName
var tableLeft = tableName , identSource = include.association.identifier
var identLeft = include.association.identifier , attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var attrLeft = 'id'
var tableRight = include.as var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
var identRight = include.association.foreignIdentifier , tableTarget = include.as
var attrRight = 'id' , identTarget = include.association.foreignIdentifier
var tableJunction = include.association.connectorDAO.tableName , attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identLeft)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight) var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
} }
}.bind(this)) }.bind(this))
......
...@@ -19,8 +19,9 @@ module.exports = (function() { ...@@ -19,8 +19,9 @@ module.exports = (function() {
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
this.pg.defaults.poolSize = this.config.pool.maxConnections this.pg.defaults.poolSize = this.config.pool.maxConnections
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
} }
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
...@@ -57,9 +58,9 @@ module.exports = (function() { ...@@ -57,9 +58,9 @@ module.exports = (function() {
}) })
.on('success', function(done) { .on('success', function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {}) var query = new Query(self.client, self.sequelize, callee, options || {})
done = done || null
return query.run(sql, done) return query.run(sql)
.complete(function(err) { done && done(err) })
.success(function(results) { self.endQuery.call(self) }) .success(function(results) { self.endQuery.call(self) })
.error(function(err) { self.endQuery.call(self) }) .error(function(err) { self.endQuery.call(self) })
.proxy(emitter) .proxy(emitter)
...@@ -113,17 +114,17 @@ module.exports = (function() { ...@@ -113,17 +114,17 @@ module.exports = (function() {
emitter.emit('success', done) emitter.emit('success', done)
}) })
} else { } else {
done && done()
self.client = null self.client = null
emitter.emit('success', done) emitter.emit('success')
} }
} }
if (this.pooling) { if (this.pooling) {
// acquire client from pool // acquire client from pool
this.poolIdentifier = this.pg.pools.getOrCreate(uri) this.pg.connect(uri, connectCallback)
this.poolIdentifier.connect(connectCallback)
} else { } else {
if (!!this.client && this.client.readyForQuery === true) { if (!!this.client) {
connectCallback(null, this.client) connectCallback(null, this.client)
} else { } else {
//create one-off client //create one-off client
...@@ -141,10 +142,9 @@ module.exports = (function() { ...@@ -141,10 +142,9 @@ module.exports = (function() {
} }
if (this.client) { if (this.client) {
this.client.end() this.client.end.bind(this.client)
} }
this.client = null
this.isConnecting = false this.isConnecting = false
this.isConnected = false this.isConnected = false
} }
......
...@@ -68,10 +68,6 @@ module.exports = (function() { ...@@ -68,10 +68,6 @@ module.exports = (function() {
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr]) var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr])
attrStr.push(this.quoteIdentifier(attr) + " " + dataType) attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
if (attributes[attr].match(/^ENUM\(/)) {
query = this.pgEnum(tableName, attr, attributes[attr]) + query
}
} }
var values = { var values = {
...@@ -262,20 +258,24 @@ module.exports = (function() { ...@@ -262,20 +258,24 @@ module.exports = (function() {
var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>' var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>'
if (!include.association.connectorDAO) { if (!include.association.connectorDAO) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
query += Utils._.template(joinQuery)({ query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName), table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as), as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName), tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: this.quoteIdentifier('id'), attrLeft: this.quoteIdentifier(((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])),
tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as), tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: this.quoteIdentifier(include.association.identifier) attrRight: this.quoteIdentifier(include.association.identifier)
}) })
} else { } else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
query += Utils._.template(joinQuery)({ query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.association.connectorDAO.tableName), table: this.quoteIdentifiers(include.association.connectorDAO.tableName),
as: this.quoteIdentifier(include.association.connectorDAO.tableName), as: this.quoteIdentifier(include.association.connectorDAO.tableName),
tableLeft: this.quoteIdentifiers(tableName), tableLeft: this.quoteIdentifiers(tableName),
attrLeft: this.quoteIdentifier('id'), attrLeft: this.quoteIdentifier(((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName), tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.identifier) attrRight: this.quoteIdentifier(include.association.identifier)
}) })
...@@ -284,7 +284,7 @@ module.exports = (function() { ...@@ -284,7 +284,7 @@ module.exports = (function() {
table: this.quoteIdentifiers(include.daoFactory.tableName), table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as), as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers(include.as), tableLeft: this.quoteIdentifiers(include.as),
attrLeft: this.quoteIdentifier('id'), attrLeft: this.quoteIdentifier(((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName), tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.foreignIdentifier) attrRight: this.quoteIdentifier(include.association.foreignIdentifier)
}) })
...@@ -723,9 +723,52 @@ module.exports = (function() { ...@@ -723,9 +723,52 @@ module.exports = (function() {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'")) return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"))
}, },
pgEnum: function (tableName, attr, dataType) { pgListEnums: function(tableName, attrName, options) {
if (arguments.length === 1) {
options = tableName
tableName = null
}
var enumName = ''
if (!!tableName && !!attrName) {
enumName = ' AND t.typname=' + this.escape("enum_" + tableName + "_" + attrName) + ' '
}
var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel) enum_value FROM pg_type t ' +
'JOIN pg_enum e ON t.oid = e.enumtypid ' +
'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
'WHERE n.nspname = \'public\' ' + enumName + ' GROUP BY 1'
return query
},
pgEnum: function (tableName, attr, dataType, options) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
var sql = "CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
if (!!options && options.force === true) {
sql = this.pgEnumDrop(tableName, attr) + sql
}
return sql
},
pgEnumAdd: function(tableName, attr, value, options) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
var sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ' + this.escape(value)
if (!!options.before) {
sql += ' BEFORE ' + this.escape(options.before)
}
else if (!!options.after) {
sql += ' AFTER ' + this.escape(options.after)
}
return sql
},
pgEnumDrop: function(tableName, attr) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr) var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; " return 'DROP TYPE IF EXISTS ' + enumName + '; '
}, },
fromArray: function(text) { fromArray: function(text) {
......
...@@ -18,7 +18,7 @@ module.exports = (function() { ...@@ -18,7 +18,7 @@ module.exports = (function() {
} }
Utils.inherit(Query, AbstractQuery) Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql, done) { Query.prototype.run = function(sql) {
this.sql = sql this.sql = sql
var self = this var self = this
...@@ -40,7 +40,6 @@ module.exports = (function() { ...@@ -40,7 +40,6 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
query.on('end', function() { query.on('end', function() {
done && done()
this.emit('sql', this.sql) this.emit('sql', this.sql)
if (receivedError) { if (receivedError) {
......
...@@ -170,26 +170,31 @@ module.exports = (function() { ...@@ -170,26 +170,31 @@ module.exports = (function() {
optAttributes = optAttributes.concat(attributes) optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) { if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
var as = include.as , tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName) , attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
var attrLeft = 'id' , tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as) , attrRight = include.association.identifier
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else { } else {
var table = include.daoFactory.tableName var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
var as = include.as , tableSource = tableName
var tableLeft = tableName , identSource = include.association.identifier
var identLeft = include.association.identifier , attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var attrLeft = 'id'
var tableRight = include.as var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
var identRight = include.association.foreignIdentifier , tableTarget = include.as
var attrRight = 'id' , identTarget = include.association.foreignIdentifier
var tableJunction = include.association.connectorDAO.tableName , attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identLeft)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight) var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
} }
}.bind(this)) }.bind(this))
......
...@@ -63,25 +63,161 @@ module.exports = (function() { ...@@ -63,25 +63,161 @@ module.exports = (function() {
} }
QueryInterface.prototype.createTable = function(tableName, attributes, options) { QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var attributeHashes = {} var attributeHashes = {}
, dataTypeValues = Utils._.values(DataTypes)
Utils._.each(attributes, function(dataTypeOrOptions, attributeName) { , keys = Object.keys(attributes)
if (Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1) { , keyLen = keys.length
attributeHashes[attributeName] = { type: dataTypeOrOptions, allowNull: true } , self = this
, sql = ''
, i = 0
for (i = 0; i < keyLen; i++) {
if (dataTypeValues.indexOf(attributes[keys[i]]) > -1) {
attributeHashes[keys[i]] = { type: attributes[keys[i]], allowNull: true }
} else { } else {
attributeHashes[attributeName] = dataTypeOrOptions attributeHashes[keys[i]] = attributes[keys[i]]
} }
}) }
return new Utils.CustomEventEmitter(function(emitter) {
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === "postgres") {
var chainer = new Utils.QueryChainer()
// For backwards-compatibility, public schemas don't need to
// explicitly state their schema when creating a new enum type
, getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].toString().match(/^ENUM\(/)) {
sql = self.QueryGenerator.pgListEnums(getTableName, keys[i], options)
chainer.add(self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT' }))
}
}
chainer.runSerially().success(function(results) {
var chainer2 = new Utils.QueryChainer()
// Find the table that we're trying to create throgh DAOFactoryManager
, daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) { return dao.tableName === tableName })
, enumIdx = 0
daoTable = daoTable.length > 0 ? daoTable[0] : null
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].toString().match(/^ENUM\(/)) {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, keys[i], attributes[keys[i]], options)
chainer2.add(self.sequelize.query(sql, null, { raw: true }))
}
else if (!!results[enumIdx] && !!daoTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values
vals.forEach(function(value, idx) {
// reset out after/before options since it's for every enum value
options.before = null
options.after = null
if (enumVals.indexOf(value) === -1) {
if (!!vals[idx+1]) {
options.before = vals[idx+1]
}
else if (!!vals[idx-1]) {
options.after = vals[idx-1]
}
chainer2.add(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options)))
}
})
}
}
}
attributes = this.QueryGenerator.attributesToSQL(attributeHashes) attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
var sql = this.QueryGenerator.createTableQuery(tableName, attributes, options) chainer2.run().success(function() {
return queryAndEmit.call(this, sql, 'createTable') queryAndEmit.call(self, sql, 'createTable')
.success(function(res) {
self.emit('createTable', null)
emitter.emit('success', res)
})
.error(function(err) {
self.emit('createTable', err)
emitter.emit('error', err)
})
.on('sql', function(sql) { emitter.emit('sql', sql) })
}).error(function(err) {
emitter.emit('error', err)
}).on('sql', function(sql) {
emitter.emit('sql', sql)
})
})
} else {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
queryAndEmit.call(self, sql, 'createTable', emitter).success(function(results) {
self.emit('createTable', null)
emitter.emit('success', results)
}).error(function(err) {
self.emit('createTable', err)
emitter.emit('error', err)
}).on('sql', function(sql) {
emitter.emit('sql', sql)
})
}
}).run()
} }
QueryInterface.prototype.dropTable = function(tableName, options) { QueryInterface.prototype.dropTable = function(tableName, options) {
var sql = this.QueryGenerator.dropTableQuery(tableName, options) // if we're forcing we should be cascading unless explicitly stated otherwise
return queryAndEmit.call(this, sql, 'dropTable') options = options || {}
options.cascade = options.cascade || options.force || false
var sql = this.QueryGenerator.dropTableQuery(tableName, options)
, self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql])
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
if (self.sequelize.options.dialect === "postgres") {
// Find the table that we're trying to drop
daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) {
return dao.tableName === tableName
})
// Just in case if we're trying to drop a non-existing table
daoTable = daoTable.length > 0 ? daoTable[0] : null
if (!!daoTable) {
var getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName
var keys = Object.keys(daoTable.rawAttributes)
, keyLen = keys.length
, i = 0
for (i = 0; i < keyLen; i++) {
if (daoTable.rawAttributes[keys[i]].type && daoTable.rawAttributes[keys[i]].type === "ENUM") {
chainer.add(self.sequelize, 'query', [self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {raw: true}])
}
}
}
}
chainer.runSerially().success(function(results) {
emitter.emit('success', results[0])
self.emit('dropTable', null)
}).error(function(err) {
emitter.emit('error', err)
self.emit('dropTable', err)
}).on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).run()
} }
QueryInterface.prototype.dropAllTables = function() { QueryInterface.prototype.dropAllTables = function() {
...@@ -156,7 +292,8 @@ module.exports = (function() { ...@@ -156,7 +292,8 @@ module.exports = (function() {
if (self.QueryGenerator.describeTableQuery) { if (self.QueryGenerator.describeTableQuery) {
sql = self.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter) sql = self.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter)
} else { } else {
sql = 'DESCRIBE ' + self.QueryGenerator.addSchema({tableName: tableName, options: {schema: schema, schemaDelimiter: schemaDelimiter}}) + ';' var table = self.QueryGenerator.quoteIdentifier(self.QueryGenerator.addSchema({tableName: tableName, options: {schema: schema, schemaDelimiter: schemaDelimiter}}), self.QueryGenerator.options.quoteIdentifiers)
sql = 'DESCRIBE ' + table + ';'
} }
self.sequelize.query(sql, null, { raw: true }).success(function(data) { self.sequelize.query(sql, null, { raw: true }).success(function(data) {
...@@ -361,8 +498,8 @@ module.exports = (function() { ...@@ -361,8 +498,8 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', []) chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete']) chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially() chainer.runSerially()
.success(function(results){ .success(function(results){
......
...@@ -192,6 +192,31 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() { ...@@ -192,6 +192,31 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
}) })
}) })
describe("Association column", function() {
it('has correct type for non-id primary keys with non-integer type', function(done) {
var User = this.sequelize.define('UserPKBT', {
username: {
type: DataTypes.STRING
}
})
, self = this
var Group = this.sequelize.define('GroupPKBT', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
})
User.belongsTo(Group)
self.sequelize.sync({ force: true }).success(function() {
expect(User.rawAttributes.GroupPKBTId.type.toString()).to.equal(DataTypes.STRING.toString())
done()
})
})
})
describe("Association options", function() { describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) { it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }) var User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING })
......
...@@ -229,6 +229,31 @@ describe(Support.getTestDialectTeaser("HasOne"), function() { ...@@ -229,6 +229,31 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
}) })
describe("Association column", function() {
it('has correct type for non-id primary keys with non-integer type', function(done) {
var User = this.sequelize.define('UserPKBT', {
username: {
type: Sequelize.STRING
}
})
, self = this
var Group = this.sequelize.define('GroupPKBT', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
Group.hasOne(User)
self.sequelize.sync({ force: true }).success(function() {
expect(User.rawAttributes.GroupPKBTId.type.toString()).to.equal(Sequelize.STRING.toString())
done()
})
})
})
describe("Association options", function() { describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) { it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }) var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
......
...@@ -1797,6 +1797,42 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1797,6 +1797,42 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
}) })
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerbelong', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group = self.sequelize.define('GroupPKeagerbelong', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.User.belongsTo(self.Group)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone', GroupPKeagerbelongId: 'people' }).success(function() {
self.Group.create({ name: 'people' }).success(function() {
self.User.find({
where: {
username: 'someone'
},
include: [self.Group]
}).complete(function (err, someUser) {
expect(err).to.be.null
expect(someUser).to.exist
expect(someUser.username).to.equal('someone')
expect(someUser.groupPKeagerbelong.name).to.equal('people')
done()
})
})
})
})
})
}) })
describe('hasOne', function() { describe('hasOne', function() {
...@@ -1830,6 +1866,42 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1830,6 +1866,42 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
}) })
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group.hasOne(self.User)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone', GroupPKeageroneId: 'people' }).success(function() {
self.Group.create({ name: 'people' }).success(function() {
self.Group.find({
where: {
name: 'people'
},
include: [self.User]
}).complete(function (err, someGroup) {
expect(err).to.be.null
expect(someGroup).to.exist
expect(someGroup.name).to.equal('people')
expect(someGroup.userPKeagerone.username).to.equal('someone')
done()
})
})
})
})
})
}) })
describe('hasOne with alias', function() { describe('hasOne with alias', function() {
...@@ -1916,6 +1988,46 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1916,6 +1988,46 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
}) })
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
self.Group.hasMany(self.User)
self.User.hasMany(self.Group)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone' }).success(function(someUser) {
self.Group.create({ name: 'people' }).success(function(someGroup) {
someUser.setGroupPKeagerones([someGroup]).complete(function (err, data) {
expect(err).to.be.null
self.User.find({
where: {
username: 'someone'
},
include: [self.Group]
}).complete(function (err, someUser) {
expect(err).to.be.null
expect(someUser).to.exist
expect(someUser.username).to.equal('someone')
expect(someUser.groupPKeagerones[0].name).to.equal('people')
done()
})
})
})
})
})
})
}) })
describe('hasMany with alias', function() { describe('hasMany with alias', function() {
......
...@@ -3,6 +3,7 @@ var chai = require('chai') ...@@ -3,6 +3,7 @@ var chai = require('chai')
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, DataTypes = require(__dirname + "/../../lib/data-types") , DataTypes = require(__dirname + "/../../lib/data-types")
, _ = require('lodash')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
...@@ -32,6 +33,67 @@ if (dialect.match(/^postgres/)) { ...@@ -32,6 +33,67 @@ if (dialect.match(/^postgres/)) {
}) })
}) })
describe('enums', function() {
it('should be able to ignore enum types that already exist', function(done) {
var User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh')
})
User.sync({ force: true }).success(function() {
User.sync().success(function() {
done()
})
})
})
it('should be able to create/drop enums multiple times', function(done) {
var User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh')
})
User.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
done()
})
})
})
it('should be able to add enum types', function(done) {
var self = this
, User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh')
})
var _done = _.after(4, function() {
done()
})
User.sync({ force: true }).success(function() {
User = self.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful')
})
User.sync().success(function() {
expect(User.rawAttributes.mood.values).to.deep.equal(['neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful'])
_done()
}).on('sql', function(sql) {
if (sql.indexOf('neutral') > -1) {
expect(sql).to.equal("ALTER TYPE \"enum_UserEnums_mood\" ADD VALUE 'neutral' BEFORE 'happy'")
_done()
}
else if (sql.indexOf('ecstatic') > -1) {
expect(sql).to.equal("ALTER TYPE \"enum_UserEnums_mood\" ADD VALUE 'ecstatic' BEFORE 'meh'")
_done()
}
else if (sql.indexOf('joyful') > -1) {
expect(sql).to.equal("ALTER TYPE \"enum_UserEnums_mood\" ADD VALUE 'joyful' AFTER 'meh'")
_done()
}
})
})
})
})
describe('integers', function() { describe('integers', function() {
describe('integer', function() { describe('integer', function() {
beforeEach(function(done) { beforeEach(function(done) {
......
...@@ -139,7 +139,7 @@ if (dialect.match(/^postgres/)) { ...@@ -139,7 +139,7 @@ if (dialect.match(/^postgres/)) {
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS \"enum_myTable_title\"; CREATE TYPE \"enum_myTable_title\" AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));"
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
...@@ -163,7 +163,7 @@ if (dialect.match(/^postgres/)) { ...@@ -163,7 +163,7 @@ if (dialect.match(/^postgres/)) {
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS enum_myTable_title; CREATE TYPE enum_myTable_title AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));", expectation: "CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!