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

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() { ...@@ -103,10 +103,24 @@ module.exports = (function() {
})() })()
DAOFactory.prototype.init = function(daoFactoryManager) { DAOFactory.prototype.init = function(daoFactoryManager) {
var self = this; var self = this
this.daoFactoryManager = daoFactoryManager this.daoFactoryManager = daoFactoryManager
this.primaryKeys = {}; 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) { Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if (dataTypeString.indexOf('PRIMARY KEY') !== -1) { if (dataTypeString.indexOf('PRIMARY KEY') !== -1) {
......
...@@ -199,6 +199,19 @@ module.exports = (function() { ...@@ -199,6 +199,19 @@ module.exports = (function() {
emitter.emit('sql', sql) emitter.emit('sql', sql)
}) })
.error(function(err) { .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) emitter.emit('error', err)
}) })
.success(function(result) { .success(function(result) {
......
...@@ -77,6 +77,12 @@ module.exports = (function() { ...@@ -77,6 +77,12 @@ module.exports = (function() {
} }
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ") , 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) { if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")" values.attributes += ", PRIMARY KEY (" + pkString + ")"
} }
...@@ -109,6 +115,19 @@ module.exports = (function() { ...@@ -109,6 +115,19 @@ module.exports = (function() {
return 'SHOW TABLES;' 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) { addColumnQuery: function(tableName, attributes) {
var query = "ALTER TABLE `<%= tableName %>` ADD <%= attributes %>;" var query = "ALTER TABLE `<%= tableName %>` ADD <%= attributes %>;"
, attrString = [] , attrString = []
...@@ -533,7 +552,7 @@ module.exports = (function() { ...@@ -533,7 +552,7 @@ module.exports = (function() {
template += " DEFAULT " + this.escape(dataType.defaultValue) template += " DEFAULT " + this.escape(dataType.defaultValue)
} }
if (dataType.unique) { if (dataType.unique === true) {
template += " UNIQUE" template += " UNIQUE"
} }
......
...@@ -44,6 +44,8 @@ module.exports = (function() { ...@@ -44,6 +44,8 @@ module.exports = (function() {
}, },
createTableQuery: function(tableName, attributes, options) { createTableQuery: function(tableName, attributes, options) {
var self = this
options = Utils._.extend({ options = Utils._.extend({
}, options || {}) }, options || {})
...@@ -76,6 +78,12 @@ module.exports = (function() { ...@@ -76,6 +78,12 @@ module.exports = (function() {
comments: Utils._.template(comments, { table: this.quoteIdentifiers(tableName)}) 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){ var pks = primaryKeys[tableName].map(function(pk){
return this.quoteIdentifier(pk) return this.quoteIdentifier(pk)
}.bind(this)).join(",") }.bind(this)).join(",")
...@@ -122,6 +130,18 @@ module.exports = (function() { ...@@ -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) { addColumnQuery: function(tableName, attributes) {
var query = "ALTER TABLE <%= tableName %> ADD COLUMN <%= attributes %>;" var query = "ALTER TABLE <%= tableName %> ADD COLUMN <%= attributes %>;"
, attrString = [] , attrString = []
...@@ -655,7 +675,7 @@ module.exports = (function() { ...@@ -655,7 +675,7 @@ module.exports = (function() {
replacements.defaultValue = this.escape(dataType.defaultValue) replacements.defaultValue = this.escape(dataType.defaultValue)
} }
if (dataType.unique) { if (dataType.unique === true) {
template += " UNIQUE" template += " UNIQUE"
} }
......
...@@ -81,6 +81,12 @@ module.exports = (function() { ...@@ -81,6 +81,12 @@ module.exports = (function() {
} }
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ") , 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) { if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")" values.attributes += ", PRIMARY KEY (" + pkString + ")"
} }
...@@ -99,6 +105,18 @@ module.exports = (function() { ...@@ -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() { addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments) var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
...@@ -318,7 +336,7 @@ module.exports = (function() { ...@@ -318,7 +336,7 @@ module.exports = (function() {
replacements.defaultValue = this.escape(dataType.defaultValue) replacements.defaultValue = this.escape(dataType.defaultValue)
} }
if (dataType.unique) { if (dataType.unique === true) {
template += " UNIQUE" template += " UNIQUE"
} }
......
...@@ -489,6 +489,38 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -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) { it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', { 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!