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

Commit 59e1c677 by Daniel Durante

Support several columns for unique indices.

You can now declare unique indices like so:

var User = sequelize.define('User', {
	username: {
		type: DataTypes.STRING,
		unique: {
			name: 'username_and_email',
			msg: 'Your username and email must be unique!'
		}
	},
	email: {
		type: DataTypes.STRING,
		unique: 'username_and_email'
	},
	aCol: {
		type: DataTypes.STRING,
		unique: true
	}
})

Will create two unique indices one for username/email combo and the
other for aCol. If a unique constraint occurs for aCol, the old behavior
still occurs (emits a dialect returned error), but for username/email
index the returned error would be, "Your username and email must be
unique!"
1 parent 524870df
......@@ -103,10 +103,24 @@ module.exports = (function() {
})()
DAOFactory.prototype.init = function(daoFactoryManager) {
var self = this;
var self = this
this.daoFactoryManager = daoFactoryManager
this.primaryKeys = {};
this.daoFactoryManager = daoFactoryManager
this.primaryKeys = {}
self.options.uniqueKeys = {}
Utils._.each(this.rawAttributes, function(columnValues, columnName) {
if (columnValues.hasOwnProperty('unique') && columnValues.unique !== true) {
var idxName = columnValues.unique
if (typeof columnValues.unique === "object") {
idxName = columnValues.unique.name
}
self.options.uniqueKeys[idxName] = self.options.uniqueKeys[idxName] || {fields: [], msg: null}
self.options.uniqueKeys[idxName].fields.push(columnName)
self.options.uniqueKeys[idxName].msg = self.options.uniqueKeys[idxName].msg || columnValues.unique.msg || null
}
})
Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if (dataTypeString.indexOf('PRIMARY KEY') !== -1) {
......@@ -359,8 +373,8 @@ module.exports = (function() {
// whereCollection is used for non-primary key updates
this.options.whereCollection = optcpy.where || null;
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
}, queryOptions))
}
......
......@@ -199,6 +199,19 @@ module.exports = (function() {
emitter.emit('sql', sql)
})
.error(function(err) {
if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) {
var fields = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString())
if (fields !== false) {
fields = fields.filter(function(f) { return f !== self.daoFactory.tableName; })
Utils._.each(self.__options.uniqueKeys, function(value, key) {
if (Utils._.isEqual(value.fields, fields) && !!value.msg) {
err = value.msg
}
})
}
}
emitter.emit('error', err)
})
.success(function(result) {
......
......@@ -77,6 +77,12 @@ module.exports = (function() {
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
values.attributes += ", UNIQUE uniq_" + tableName + '_' + columns.fields.join('_') + " (" + columns.fields.join(', ') + ")"
})
}
if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")"
}
......@@ -109,6 +115,19 @@ module.exports = (function() {
return 'SHOW TABLES;'
},
uniqueConstraintMapping: {
code: 'ER_DUP_ENTRY',
map: function(str) {
// we're manually remvoving uniq_ here for a future capability of defining column names explicitly
var match = str.replace('uniq_', '').match(/Duplicate entry .* for key '(.*?)'$/)
if (match === null || match.length < 2) {
return false
}
return match[1].split('_')
}
},
addColumnQuery: function(tableName, attributes) {
var query = "ALTER TABLE `<%= tableName %>` ADD <%= attributes %>;"
, attrString = []
......@@ -533,7 +552,7 @@ module.exports = (function() {
template += " DEFAULT " + this.escape(dataType.defaultValue)
}
if (dataType.unique) {
if (dataType.unique === true) {
template += " UNIQUE"
}
......
......@@ -44,6 +44,8 @@ module.exports = (function() {
},
createTableQuery: function(tableName, attributes, options) {
var self = this
options = Utils._.extend({
}, options || {})
......@@ -76,6 +78,12 @@ module.exports = (function() {
comments: Utils._.template(comments, { table: this.quoteIdentifiers(tableName)})
}
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
values.attributes += ", UNIQUE (" + columns.fields.map(function(f) { return self.quoteIdentifiers(f) }).join(', ') + ")"
})
}
var pks = primaryKeys[tableName].map(function(pk){
return this.quoteIdentifier(pk)
}.bind(this)).join(",")
......@@ -122,6 +130,18 @@ module.exports = (function() {
})
},
uniqueConstraintMapping: {
code: '23505',
map: function(str) {
var match = str.match(/duplicate key value violates unique constraint "(.*?)_key"/)
if (match === null || match.length < 2) {
return false
}
return [1].split('_').splice(1)
}
},
addColumnQuery: function(tableName, attributes) {
var query = "ALTER TABLE <%= tableName %> ADD COLUMN <%= attributes %>;"
, attrString = []
......@@ -655,7 +675,7 @@ module.exports = (function() {
replacements.defaultValue = this.escape(dataType.defaultValue)
}
if (dataType.unique) {
if (dataType.unique === true) {
template += " UNIQUE"
}
......
......@@ -81,6 +81,12 @@ module.exports = (function() {
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
values.attributes += ", UNIQUE (" + columns.fields.join(', ') + ")"
})
}
if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")"
}
......@@ -99,6 +105,18 @@ module.exports = (function() {
})
},
uniqueConstraintMapping: {
code: 'SQLITE_CONSTRAINT',
map: function(str) {
var match = str.match(/columns (.*?) are/)
if (match === null || match.length < 2) {
return false
}
return match[1].split(', ')
}
},
addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql)
......@@ -179,7 +197,7 @@ module.exports = (function() {
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
, 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)
} else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
......@@ -318,7 +336,7 @@ module.exports = (function() {
replacements.defaultValue = this.escape(dataType.defaultValue)
}
if (dataType.unique) {
if (dataType.unique === true) {
template += " UNIQUE"
}
......
......@@ -489,6 +489,38 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('allows multiple column unique keys to be defined', function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: 'user_and_email' },
email: { type: Sequelize.STRING, unique: 'user_and_email' },
aCol: { type: Sequelize.STRING, unique: 'a_and_b' },
bCol: { type: Sequelize.STRING, unique: 'a_and_b' }
})
User.sync({ force: true }).on('sql', function(sql) {
expect(sql).to.match(/UNIQUE \(username, email\)/)
expect(sql).to.match(/UNIQUE \(aCol, bCol\)/)
done()
})
})
it('allows us to customize the error message for unique constraint', function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: { name: 'user_and_email', msg: 'User and email must be unique' }},
email: { type: Sequelize.STRING, unique: 'user_and_email' },
aCol: { type: Sequelize.STRING, unique: 'a_and_b' },
bCol: { type: Sequelize.STRING, unique: 'a_and_b' }
})
User.sync({ force: true }).success(function() {
User.create({username: 'tobi', email: 'tobi@tobi.me'}).success(function() {
User.create({username: 'tobi', email: 'tobi@tobi.me'}).error(function(err) {
expect(err).to.equal('User and email must be unique')
done()
})
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!