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

Commit fb37f064 by Daniel Durante

ENUM types will no longer in Postgres

Postgres will now add enum's values on .sync() if you were to add
entires into the attribute's.value. It'll also add the enum type in
the proper order. For example:

```js
Table = sequelize.define...
  mood: {
    type: DataTypes.ENUM,
    values: ['happy', 'sad']
  }

// For some reason down the road you redefine the same table
// ...
  mood: {
    type: DataTypes.ENUM,
    values: ['neutral', 'happy', 'sad', 'joyful']
  }
```

Will result in the following SQL:

```sql
ALTER TYPE "...enumTypeName..." ADD VALUE 'neutral' BEFORE 'happy';
ALTER TYPE "...enumTypeName..." ADD VALUE 'jouful' AFTER 'sad';
```

- ```.drop()``` will now pass the options object through (optional)
- Added Postgres.QueryGenerator.pgListEnums(tableName=null,
   attrName=null, options) which lists all of the ENUM types for
   Postgres, if you don't specify an attribute and tablename then
   Sequelize will list all of the Enums
- Added Postgres.QueryGenerator.pgEnumAdd(tableName, attr, value,
   options) which will alter the enum type and add values
- Added Postgres.QueryGenerator.pgEnumDrop(tableName, attr) which
   will drop the enum type
- Postgres.QueryGenerator.pgEnum() will no longer try to drop the
   type unless {force: true} within the ```.sync()``` commands
- Refactored ```QueryInterface.createTable()``` in order to allow
   Postgres to create enum types explicitly rather than from
   Postgres.```QueryGenerator.createTable()```
- Refactored QueryInterface.dropTable() -- same as ```createTable()```
   changes .. also {force: true/force} will now set options.cascade
- QueryInterface.dropAllTables() will now correctly add a quote
   identifier to table names for MySQL

Closes https://github.com/sequelize/sequelize/issues/546
1 parent c2d59e20
......@@ -198,15 +198,17 @@ module.exports = (function() {
}
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 {
doQuery()
}
}).run()
}
DAOFactory.prototype.drop = function() {
return this.QueryInterface.dropTable(this.getTableName(this.tableName))
DAOFactory.prototype.drop = function(options) {
// Only Postgres' QueryGenerator.dropTableQuery() will add schema manually
var tableName = !!options && !!options.schema && this.options.dialect === "postgres" ? this.tableName : this.getTableName()
return this.QueryInterface.dropTable(tableName, options)
}
DAOFactory.prototype.dropSchema = function(schema) {
......
......@@ -68,10 +68,6 @@ module.exports = (function() {
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr])
attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
if (attributes[attr].match(/^ENUM\(/)) {
query = this.pgEnum(tableName, attr, attributes[attr]) + query
}
}
var values = {
......@@ -723,9 +719,52 @@ module.exports = (function() {
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)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
return 'DROP TYPE IF EXISTS ' + enumName + '; '
},
fromArray: function(text) {
......
......@@ -64,24 +64,158 @@ module.exports = (function() {
QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var attributeHashes = {}
Utils._.each(attributes, function(dataTypeOrOptions, attributeName) {
if (Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1) {
attributeHashes[attributeName] = { type: dataTypeOrOptions, allowNull: true }
, dataTypeValues = Utils._.values(DataTypes)
, keys = Object.keys(attributes)
, keyLen = keys.length
, 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 {
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)
chainer2.run().success(function() {
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)
var sql = this.QueryGenerator.createTableQuery(tableName, attributes, options)
return queryAndEmit.call(this, sql, 'createTable')
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) {
// if we're forcing we should be cascading unless explicitly stated otherwise
options = options || {}
options.cascade = options.cascade || options.force || false
var sql = this.QueryGenerator.dropTableQuery(tableName, options)
return queryAndEmit.call(this, sql, 'dropTable')
, 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 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(tableName, 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() {
......@@ -156,7 +290,8 @@ module.exports = (function() {
if (self.QueryGenerator.describeTableQuery) {
sql = self.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter)
} 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) {
......
......@@ -3,6 +3,7 @@ var chai = require('chai')
, Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, DataTypes = require(__dirname + "/../../lib/data-types")
, _ = require('lodash')
chai.Assertion.includeStack = true
......@@ -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('integer', function() {
beforeEach(function(done) {
......
......@@ -139,7 +139,7 @@ if (dialect.match(/^postgres/)) {
},
{
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'}],
......@@ -163,7 +163,7 @@ if (dialect.match(/^postgres/)) {
},
{
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}}
},
{
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!