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

Commit 1c56a73f by Mick Hansen

Merge pull request #2760 from mbroadst/unique-constraint-to-validation

make UniqueConstraintError inherit ValidationError
2 parents cc127b54 bd751645
...@@ -4,22 +4,7 @@ var Utils = require('../../utils'); ...@@ -4,22 +4,7 @@ var Utils = require('../../utils');
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
dialect: 'mariadb', dialect: 'mariadb',
uniqueConstraintMapping: {
code: 1062,
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 {
indexName: match[1],
fields: match[1].split('_')
};
}
}
}; };
// "MariaDB is a drop-in replacement for MySQL." - so thats exactly what we do, drop in the mysql query generator // "MariaDB is a drop-in replacement for MySQL." - so thats exactly what we do, drop in the mysql query generator
......
...@@ -581,23 +581,7 @@ module.exports = (function() { ...@@ -581,23 +581,7 @@ module.exports = (function() {
booleanValue: function(value) { booleanValue: function(value) {
return !!value ? 1 : 0; return !!value ? 1 : 0;
}, }
uniqueConstraintMapping: {
code: 'EREQUEST',
map: function(str) {
var match = str.match(/Violation of UNIQUE KEY constraint '(.*)'. Cannot insert duplicate key in object '?(.*?)$/);
match = match || str.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/);
if (match === null || match.length < 2) {
return false;
}
return {
indexName: match[1],
fields: match[1].split('_')
};
}
},
}; };
// private methods // private methods
......
...@@ -163,14 +163,32 @@ module.exports = (function() { ...@@ -163,14 +163,32 @@ module.exports = (function() {
Query.prototype.formatError = function (err) { Query.prototype.formatError = function (err) {
var match; var match;
match = err.message.match(/Violation of UNIQUE KEY constraint '(.*)'. Cannot insert duplicate key in object '?(.*?)$/); match = err.message.match(/Violation of UNIQUE KEY constraint '(.*)'. Cannot insert duplicate key in object '.*'. The duplicate key value is \((.*)\)./);
match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/); match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/);
if (match && match.length > 1) { if (match && match.length > 1) {
var fields = {}
, message = 'Validation error'
, uniqueKey = this.callee.__options.uniqueKeys[match[1]];
if (!!uniqueKey.msg) message = uniqueKey.msg;
if (!!match[2]) {
var values = match[2].split(',').map(Function.prototype.call, String.prototype.trim);
if (!!uniqueKey) {
fields = Utils._.zipObject(uniqueKey.fields, values);
} else {
fields[match[1]] = match[2];
}
}
var errors = [];
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
return new sequelizeErrors.UniqueConstraintError({ return new sequelizeErrors.UniqueConstraintError({
name: 'SequelizeUniqueConstraintError', message: message,
fields: null, errors: errors,
index: 0,
value: match[2],
parent: err parent: err
}); });
} }
......
...@@ -99,22 +99,6 @@ module.exports = (function() { ...@@ -99,22 +99,6 @@ 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 {
indexName: match[1],
fields: match[1].split('_')
};
}
},
addColumnQuery: function(table, key, dataType) { addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;' var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attribute = Utils._.template('<%= key %> <%= definition %>')({ , attribute = Utils._.template('<%= key %> <%= definition %>')({
......
...@@ -112,10 +112,27 @@ module.exports = (function() { ...@@ -112,10 +112,27 @@ module.exports = (function() {
case 1062: case 1062:
match = err.message.match(/Duplicate entry '(.*)' for key '?(.*?)'?$/); match = err.message.match(/Duplicate entry '(.*)' for key '?(.*?)'?$/);
var values = match[1].split('-')
, fields = {}
, message = 'Validation error'
, uniqueKey = this.callee.__options.uniqueKeys[match[2]];
if (!!uniqueKey) {
if (!!uniqueKey.msg) message = uniqueKey.msg;
fields = Utils._.zipObject(uniqueKey.fields, values);
} else {
fields[match[2]] = match[1];
}
var errors = [];
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
return new sequelizeErrors.UniqueConstraintError({ return new sequelizeErrors.UniqueConstraintError({
fields: null, message: message,
index: match[2], errors: errors,
value: match[1],
parent: err parent: err
}); });
......
...@@ -165,29 +165,6 @@ module.exports = (function() { ...@@ -165,29 +165,6 @@ module.exports = (function() {
} }
}, },
uniqueConstraintMapping: {
code: '23505',
map: function(str) {
var match = str.match(/duplicate key value violates unique constraint "(.*?)"/);
if (match === null || match.length < 2) {
return false;
}
var indexName = match[1];
var fields = [];
match = indexName.match(/(.*?)_key/);
if (!!match && match.length > 1) {
fields = match[1].split('_').splice(1);
}
return {
indexName: indexName,
fields: fields
};
}
},
addColumnQuery: function(table, key, dataType) { addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;' var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;'
, dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'}) , dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'})
......
...@@ -266,18 +266,34 @@ module.exports = (function() { ...@@ -266,18 +266,34 @@ module.exports = (function() {
match = err.detail.match(/Key \((.*?)\)=\((.*?)\)/); match = err.detail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) { if (match) {
var fields = Utils._.zipObject(match[1].split(', '), match[2].split(', '))
, errors = []
, message = 'Validation error';
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
Utils._.forOwn(this.callee.__options.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
return new sequelizeErrors.UniqueConstraintError({ return new sequelizeErrors.UniqueConstraintError({
fields: match[1].split(', '), message: message,
value: match[2].split(', '), errors: errors,
index: null,
parent: err parent: err
}); });
} else { } else {
return new sequelizeErrors.UniqueConstraintError({ return new sequelizeErrors.UniqueConstraintError({
error: err, message: err.message,
message: err.message parent: err
}); });
} }
break; break;
default: default:
return new sequelizeErrors.DatabaseError(err); return new sequelizeErrors.DatabaseError(err);
......
...@@ -121,29 +121,6 @@ module.exports = (function() { ...@@ -121,29 +121,6 @@ module.exports = (function() {
return !!value ? 1 : 0; return !!value ? 1 : 0;
}, },
uniqueConstraintMapping: {
code: 'SQLITE_CONSTRAINT',
map: function(str) {
var match = str.match(/columns (.*?) are/); // Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique
if (match !== null && match.length >= 2) {
return {
fields: match[1].split(', ')
};
}
match = str.match(/UNIQUE constraint failed: (.*)/); // Sqlite 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y
if (match !== null && match.length >= 2) {
return {
fields: match[1].split(', ').map(function (columnWithTable) {
return columnWithTable.split('.')[1];
})
};
}
return false;
}
},
addLimitAndOffset: function(options, model){ addLimitAndOffset: function(options, model){
var fragment = ''; var fragment = '';
if (options.offset && !options.limit) { if (options.offset && !options.limit) {
......
...@@ -189,25 +189,6 @@ module.exports = (function() { ...@@ -189,25 +189,6 @@ module.exports = (function() {
switch (err.code) { switch (err.code) {
case 'SQLITE_CONSTRAINT': case 'SQLITE_CONSTRAINT':
match = err.message.match(/columns (.*?) are/); // Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique
if (match !== null && match.length >= 2) {
return new sequelizeErrors.UniqueConstraintError(match[1].split(', '),null, err);
}
match = err.message.match(/UNIQUE constraint failed: (.*)/); // Sqlite 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y
if (match !== null && match.length >= 2) {
var fields = match[1].split(', ').map(function (columnWithTable) {
return columnWithTable.split('.')[1];
});
return new sequelizeErrors.UniqueConstraintError({
fields: fields,
index: null,
value: null,
parent: err
});
}
match = err.message.match(/FOREIGN KEY constraint failed/); match = err.message.match(/FOREIGN KEY constraint failed/);
if (match !== null) { if (match !== null) {
return new sequelizeErrors.ForeignKeyConstraintError({ return new sequelizeErrors.ForeignKeyConstraintError({
...@@ -215,7 +196,45 @@ module.exports = (function() { ...@@ -215,7 +196,45 @@ module.exports = (function() {
}); });
} }
return err; var fields = [];
// Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique
match = err.message.match(/columns (.*?) are/);
if (match !== null && match.length >= 2) {
fields = match[1].split(', ');
} else {
// Sqlite post 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y
match = err.message.match(/UNIQUE constraint failed: (.*)/);
if (match !== null && match.length >= 2) {
fields = match[1].split(', ').map(function (columnWithTable) {
return columnWithTable.split('.')[1];
});
}
}
var errors = []
, self = this
, message = 'Validation error';
fields.forEach(function(field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, self.callee[field]));
});
Utils._.forOwn(this.callee.__options.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, fields) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err
});
case 'SQLITE_BUSY': case 'SQLITE_BUSY':
return new sequelizeErrors.TimeoutError(err); return new sequelizeErrors.TimeoutError(err);
......
...@@ -105,7 +105,7 @@ error.TimeoutError = function (parent) { ...@@ -105,7 +105,7 @@ error.TimeoutError = function (parent) {
}; };
util.inherits(error.TimeoutError, error.BaseError); util.inherits(error.TimeoutError, error.BaseError);
/** /**
* Thrown when a unique constraint is violated in the database * Thrown when a unique constraint is violated in the database
* @extends DatabaseError * @extends DatabaseError
* @constructor * @constructor
...@@ -113,16 +113,15 @@ util.inherits(error.TimeoutError, error.BaseError); ...@@ -113,16 +113,15 @@ util.inherits(error.TimeoutError, error.BaseError);
error.UniqueConstraintError = function (options) { error.UniqueConstraintError = function (options) {
options = options || {}; options = options || {};
options.parent = options.parent || { sql: '' }; options.parent = options.parent || { sql: '' };
options.message = options.message || 'Validation error';
options.errors = options.errors || {};
error.DatabaseError.call(this, options.parent); error.ValidationError.call(this, options.message, options.errors);
this.name = 'SequelizeUniqueConstraintError'; this.name = 'SequelizeUniqueConstraintError';
this.message = options.message; this.message = options.message;
this.fields = options.fields; this.errors = options.errors;
this.value = options.value;
this.index = options.index;
}; };
util.inherits(error.UniqueConstraintError, error.DatabaseError); util.inherits(error.UniqueConstraintError, error.ValidationError);
/** /**
* Thrown when a foreign key constraint is violated in the database * Thrown when a foreign key constraint is violated in the database
......
...@@ -602,53 +602,34 @@ module.exports = (function() { ...@@ -602,53 +602,34 @@ module.exports = (function() {
}); });
} }
}).then(function() { }).then(function() {
return self.QueryInterface[query].apply(self.QueryInterface, args).catch(self.sequelize.UniqueConstraintError, function(err) { return self.QueryInterface[query].apply(self.QueryInterface, args)
if (!!self.__options.uniqueKeys && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.parent.code) { .tap(function(result) {
var index = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.parent.toString()); // Transfer database generated values (defaults, autoincrement, etc)
Object.keys(self.Model.rawAttributes).forEach(function (attr) {
if (index !== false) { if (self.Model.rawAttributes[attr].field &&
var fields = index.fields.filter(function(f) { return f !== self.Model.tableName; }); values[self.Model.rawAttributes[attr].field] !== undefined &&
Utils._.each(self.__options.uniqueKeys, function(uniqueKey) { self.Model.rawAttributes[attr].field !== attr) {
if (!!uniqueKey.msg) { values[attr] = values[self.Model.rawAttributes[attr].field];
if (uniqueKey.name === index.indexName) { delete values[self.Model.rawAttributes[attr].field];
fields = _.clone(uniqueKey.fields); }
} });
if (Utils._.isEqual(uniqueKey.fields, fields)) { values = _.extend(values, result.dataValues);
err = new self.sequelize.UniqueConstraintError({
message: uniqueKey.msg,
fields: fields,
index: index.indexName,
parent: err.parent
});
}
}
});
}
}
throw err;
}).tap(function(result) {
// Transfer database generated values (defaults, autoincrement, etc)
Object.keys(self.Model.rawAttributes).forEach(function (attr) {
if (self.Model.rawAttributes[attr].field && values[self.Model.rawAttributes[attr].field] !== undefined && self.Model.rawAttributes[attr].field !== attr) {
values[attr] = values[self.Model.rawAttributes[attr].field];
delete values[self.Model.rawAttributes[attr].field];
}
});
values = _.extend(values, result.dataValues);
// Ensure new values are on Instance, and reset previousDataValues // Ensure new values are on Instance, and reset previousDataValues
result.dataValues = _.extend(result.dataValues, values); result.dataValues = _.extend(result.dataValues, values);
options.fields.forEach(function (field) { options.fields.forEach(function (field) {
result._previousDataValues[field] = result.dataValues[field]; result._previousDataValues[field] = result.dataValues[field];
});
})
.tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('after' + hook, result, options);
}
})
.then(function(result) {
return result;
}); });
}).tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('after' + hook, result, options);
}
}).then(function(result) {
return result;
});
}); });
}); });
}; };
......
...@@ -129,26 +129,40 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), function () { ...@@ -129,26 +129,40 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), function () {
}); });
describe('Constraint error', function () { describe('Constraint error', function () {
it('Can be intercepted using .catch', function () { [
var spy = sinon.spy() {
, User = this.sequelize.define('user', { type: 'UniqueConstraintError',
first_name: { exception: Sequelize.UniqueConstraintError
type: Sequelize.STRING, },
unique: 'unique_name' {
}, type: 'ValidationError',
last_name: { exception: Sequelize.ValidationError
type: Sequelize.STRING, }
unique: 'unique_name' ].forEach(function(constraintTest) {
}
it('Can be intercepted as ' + constraintTest.type + ' using .catch', function () {
var spy = sinon.spy()
, User = this.sequelize.define('user', {
first_name: {
type: Sequelize.STRING,
unique: 'unique_name'
},
last_name: {
type: Sequelize.STRING,
unique: 'unique_name'
}
});
var record = { first_name: 'jan', last_name: 'meier' };
return this.sequelize.sync({ force: true }).bind(this).then(function () {
return User.create(record);
}).then(function () {
return User.create(record).catch(constraintTest.exception, spy);
}).then(function () {
expect(spy).to.have.been.calledOnce;
}); });
return this.sequelize.sync({ force: true }).bind(this).then(function () {
return User.create({ first_name: 'jan', last_name: 'meier' });
}).then(function () {
return User.create({ first_name: 'jan', last_name: 'meier' }).catch(this.sequelize.UniqueConstraintError, spy);
}).then(function () {
expect(spy).to.have.been.calledOnce;
}); });
}); });
}); });
}); });
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!