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

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');
module.exports = (function() {
var QueryGenerator = {
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('_')
};
}
}
dialect: 'mariadb',
};
// "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() {
booleanValue: function(value) {
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
......
......@@ -163,14 +163,32 @@ module.exports = (function() {
Query.prototype.formatError = function (err) {
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 '(.*)'/);
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({
name: 'SequelizeUniqueConstraintError',
fields: null,
index: 0,
value: match[2],
message: message,
errors: errors,
parent: err
});
}
......
......@@ -99,22 +99,6 @@ 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 {
indexName: match[1],
fields: match[1].split('_')
};
}
},
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attribute = Utils._.template('<%= key %> <%= definition %>')({
......
......@@ -112,10 +112,27 @@ module.exports = (function() {
case 1062:
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({
fields: null,
index: match[2],
value: match[1],
message: message,
errors: errors,
parent: err
});
......
......@@ -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) {
var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;'
, dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'})
......
......@@ -266,18 +266,34 @@ module.exports = (function() {
match = err.detail.match(/Key \((.*?)\)=\((.*?)\)/);
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({
fields: match[1].split(', '),
value: match[2].split(', '),
index: null,
message: message,
errors: errors,
parent: err
});
} else {
return new sequelizeErrors.UniqueConstraintError({
error: err,
message: err.message
message: err.message,
parent: err
});
}
break;
default:
return new sequelizeErrors.DatabaseError(err);
......
......@@ -121,29 +121,6 @@ module.exports = (function() {
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){
var fragment = '';
if (options.offset && !options.limit) {
......
......@@ -189,25 +189,6 @@ module.exports = (function() {
switch (err.code) {
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/);
if (match !== null) {
return new sequelizeErrors.ForeignKeyConstraintError({
......@@ -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':
return new sequelizeErrors.TimeoutError(err);
......
......@@ -105,7 +105,7 @@ error.TimeoutError = function (parent) {
};
util.inherits(error.TimeoutError, error.BaseError);
/**
/**
* Thrown when a unique constraint is violated in the database
* @extends DatabaseError
* @constructor
......@@ -113,16 +113,15 @@ util.inherits(error.TimeoutError, error.BaseError);
error.UniqueConstraintError = function (options) {
options = options || {};
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.message = options.message;
this.fields = options.fields;
this.value = options.value;
this.index = options.index;
this.errors = options.errors;
};
util.inherits(error.UniqueConstraintError, error.DatabaseError);
util.inherits(error.UniqueConstraintError, error.ValidationError);
/**
* Thrown when a foreign key constraint is violated in the database
......
......@@ -602,53 +602,34 @@ module.exports = (function() {
});
}
}).then(function() {
return self.QueryInterface[query].apply(self.QueryInterface, args).catch(self.sequelize.UniqueConstraintError, function(err) {
if (!!self.__options.uniqueKeys && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.parent.code) {
var index = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.parent.toString());
if (index !== false) {
var fields = index.fields.filter(function(f) { return f !== self.Model.tableName; });
Utils._.each(self.__options.uniqueKeys, function(uniqueKey) {
if (!!uniqueKey.msg) {
if (uniqueKey.name === index.indexName) {
fields = _.clone(uniqueKey.fields);
}
if (Utils._.isEqual(uniqueKey.fields, fields)) {
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);
return self.QueryInterface[query].apply(self.QueryInterface, args)
.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
result.dataValues = _.extend(result.dataValues, values);
options.fields.forEach(function (field) {
result._previousDataValues[field] = result.dataValues[field];
// Ensure new values are on Instance, and reset previousDataValues
result.dataValues = _.extend(result.dataValues, values);
options.fields.forEach(function (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 () {
});
describe('Constraint error', function () {
it('Can be intercepted 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'
}
[
{
type: 'UniqueConstraintError',
exception: Sequelize.UniqueConstraintError
},
{
type: 'ValidationError',
exception: Sequelize.ValidationError
}
].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!