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

Commit 4a4c976a by Jan Aagaard Meier

First step towards implementing an internal transaction in findOrCreate

1 parent f4ab84c9
...@@ -96,7 +96,7 @@ module.exports = (function() { ...@@ -96,7 +96,7 @@ module.exports = (function() {
errorDetected = true; errorDetected = true;
self.promise.emit('sql', self.sql); self.promise.emit('sql', self.sql);
err.sql = sql; err.sql = sql;
self.reject(err); self.reject(self.formatError(err));
}) })
.on('end', function(info) { .on('end', function(info) {
if (alreadyEnded || errorDetected) { if (alreadyEnded || errorDetected) {
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var Utils = require('../../utils') var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, uuid = require('node-uuid'); , uuid = require('node-uuid')
, sequelizeErrors = require('../../errors.js');
module.exports = (function() { module.exports = (function() {
var Query = function(connection, sequelize, callee, options) { var Query = function(connection, sequelize, callee, options) {
...@@ -35,7 +36,8 @@ module.exports = (function() { ...@@ -35,7 +36,8 @@ module.exports = (function() {
if (err) { if (err) {
err.sql = sql; err.sql = sql;
reject(err);
reject(self.formatError(err));
} else { } else {
resolve(self.formatResults(results)); resolve(self.formatResults(results));
} }
...@@ -46,6 +48,24 @@ module.exports = (function() { ...@@ -46,6 +48,24 @@ module.exports = (function() {
return promise; return promise;
}; };
Query.prototype.formatError = function (err) {
var match;
switch (err.errno || err.code) {
case 1062:
match = err.message.match(/Duplicate entry '(.*)' for key '(.*?)'$/);
return new sequelizeErrors.UniqueConstraintError({
fields: null,
index: match[2],
value: match[1],
parent: err
});
}
return new sequelizeErrors.DatabaseError(err);
};
Query.prototype.isShowIndexesQuery = function () { Query.prototype.isShowIndexesQuery = function () {
return this.sql.toLowerCase().indexOf('show index from') === 0; return this.sql.toLowerCase().indexOf('show index from') === 0;
}; };
......
...@@ -5,7 +5,8 @@ var Utils = require('../../utils') ...@@ -5,7 +5,8 @@ var Utils = require('../../utils')
, DataTypes = require('../../data-types') , DataTypes = require('../../data-types')
, hstore = require('./hstore') , hstore = require('./hstore')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
, Promise = require('../../promise'); , Promise = require('../../promise')
, sequelizeErrors = require('../../errors.js');
// Parses hstore fields if the model has any hstore fields. // Parses hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT. // This cannot be done in the 'pg' lib because hstore is a UDT.
...@@ -61,7 +62,7 @@ module.exports = (function() { ...@@ -61,7 +62,7 @@ module.exports = (function() {
receivedError = true; receivedError = true;
err.sql = sql; err.sql = sql;
promise.emit('sql', sql, self.client.uuid); promise.emit('sql', sql, self.client.uuid);
reject(err); reject(self.formatError(err));
}); });
query.on('end', function(result) { query.on('end', function(result) {
...@@ -221,6 +222,24 @@ module.exports = (function() { ...@@ -221,6 +222,24 @@ module.exports = (function() {
return this; return this;
}; };
Query.prototype.formatError = function (err) {
var match;
switch (err.code) {
case '23505':
match = err.detail.match(/Key \((.*?)\)=\((.*?)\) already exists/);
return new sequelizeErrors.UniqueConstraintError({
fields: match[1].split(', '),
value: match[2].split(', '),
index: null,
parent: err
});
}
return new sequelizeErrors.DatabaseError(err);
};
Query.prototype.isShowIndexesQuery = function () { Query.prototype.isShowIndexesQuery = function () {
return this.sql.indexOf('pg_get_indexdef') !== -1; return this.sql.indexOf('pg_get_indexdef') !== -1;
}; };
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var Utils = require('../../utils') var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, QueryTypes = require('../../query-types'); , QueryTypes = require('../../query-types')
, sequelizeErrors = require('../../errors.js');
module.exports = (function() { module.exports = (function() {
var Query = function(database, sequelize, callee, options) { var Query = function(database, sequelize, callee, options) {
...@@ -48,10 +49,9 @@ module.exports = (function() { ...@@ -48,10 +49,9 @@ module.exports = (function() {
self.database[self.getDatabaseMethod()](self.sql, function(err, results) { self.database[self.getDatabaseMethod()](self.sql, function(err, results) {
// allow clients to listen to sql to do their own logging or whatnot // allow clients to listen to sql to do their own logging or whatnot
promise.emit('sql', self.sql, self.options.uuid); promise.emit('sql', self.sql, self.options.uuid);
if (err) { if (err) {
err.sql = self.sql; err.sql = self.sql;
reject(err); reject(self.formatError(err));
} else { } else {
var metaData = this; var metaData = this;
metaData.columnTypes = columnTypes; metaData.columnTypes = columnTypes;
...@@ -173,6 +173,36 @@ module.exports = (function() { ...@@ -173,6 +173,36 @@ module.exports = (function() {
}); });
}; };
Query.prototype.formatError = function (err) {
var match;
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
});
}
return err;
}
return new sequelizeErrors.DatabaseError(err);
};
Query.prototype.handleShowIndexesQuery = function (data) { Query.prototype.handleShowIndexesQuery = function (data) {
var self = this; var self = this;
......
...@@ -57,6 +57,26 @@ error.ValidationError.prototype.get = function(path) { ...@@ -57,6 +57,26 @@ error.ValidationError.prototype.get = function(path) {
}, []); }, []);
}; };
error.DatabaseError = function (parent) {
error.BaseError.apply(this, arguments);
this.name = 'SequelizeDatabaseError';
this.parent = parent;
this.sql = parent.sql;
};
util.inherits(error.DatabaseError, error.BaseError);
error.UniqueConstraintError = function (options) {
error.DatabaseError.call(this, options.parent);
this.name = 'SequelizeUniqueConstraintError';
this.message = options.message;
this.fields = options.fields;
this.value = options.value;
this.index = options.index;
};
util.inherits(error.UniqueConstraintError, error.DatabaseError);
/** /**
* An array of ValidationErrorItems * An array of ValidationErrorItems
* @property errors * @property errors
......
...@@ -568,21 +568,25 @@ module.exports = (function() { ...@@ -568,21 +568,25 @@ module.exports = (function() {
}); });
} }
}).then(function() { }).then(function() {
return self.QueryInterface[query].apply(self.QueryInterface, args).catch(function(err) { return self.QueryInterface[query].apply(self.QueryInterface, args).catch(self.sequelize.UniqueConstraintError, function(err) {
if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) { if (!!self.__options.uniqueKeys && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.parent.code) {
var index = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString()); var index = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.parent.toString());
if (index !== false) { if (index !== false) {
var fields = index.fields.filter(function(f) { return f !== self.Model.tableName; }); var fields = index.fields.filter(function(f) { return f !== self.Model.tableName; });
Utils._.each(self.__options.uniqueKeys, function(uniqueKey) { Utils._.each(self.__options.uniqueKeys, function(uniqueKey) {
if (!!uniqueKey.msg && (Utils._.isEqual(uniqueKey.fields, fields)) || uniqueKey.name === index.indexName) { if (!!uniqueKey.msg && (Utils._.isEqual(uniqueKey.fields, fields)) || uniqueKey.name === index.indexName) {
err = new Error(uniqueKey.msg); err = new self.sequelize.UniqueConstraintError({
message: uniqueKey.msg,
fields: fields,
index: index.indexName,
parent: err.parent
});
} }
}); });
} }
} }
throw err; throw err;
}).tap(function(result) { }).tap(function(result) {
// Transfer database generated values (defaults, autoincrement, etc) // Transfer database generated values (defaults, autoincrement, etc)
......
...@@ -1077,43 +1077,71 @@ module.exports = (function() { ...@@ -1077,43 +1077,71 @@ module.exports = (function() {
* Find a row that matches the query, or build and save the row if none is found * Find a row that matches the query, or build and save the row if none is found
* The successfull result of the promise will be (instance, created) - Make sure to use .spread() * The successfull result of the promise will be (instance, created) - Make sure to use .spread()
* *
* @param {Object} where A hash of search attributes. Note that this method differs from finders, in that the syntax is `{ attr1: 42 }` and NOT `{ where: { attr1: 42}}`. This is subject to change in 2.0 * @param {Object} [options]
* @param {Object} [defaults] Default values to use if creating a new instance * @param {Object} [options.where] where A hash of search attributes. Note that this method differs from finders, in that the syntax is `{ attr1: 42 }` and NOT `{ where: { attr1: 42}}`. This is subject to change in 2.0
* @param {Object} [options] Options passed to the find and create calls * @param {Object} [options.defaults] Default values to use if creating a new instance
* @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API * @param {Object} [queryOptions] Options passed to the find and create calls
* *
* @return {Promise<Instance>} * @return {Promise<Instance,created>}
*/ */
Model.prototype.findOrCreate = function(where, defaults, options) { Model.prototype.findOrCreate = function(options, queryOptions) {
var self = this var self = this
, internalTransaction = !(queryOptions && queryOptions.transaction)
, values = {}; , values = {};
options = Utils._.extend({ queryOptions = queryOptions ? Utils._.clone(queryOptions) : {};
transaction: null
}, options || {}); if (!options.where) {
throw new Error('Missing where attribute in the first parameter passed to findOrCreate. Please note that the API has changed, and is now options (an object with where and defaults keys), queryOptions (transaction etc.)');
}
if (!(where instanceof Utils.or) && !(where instanceof Utils.and) && !Array.isArray(where)) { if (!options.where._isSequelizeMethod && !Array.isArray(options.where)) {
for (var attrname in where) { for (var attrname in options.where) {
values[attrname] = where[attrname]; values[attrname] = options.where[attrname];
} }
} }
return self.find({ // Create a transaction or a savepoint, depending on whether a transaction was passed in
where: where return self.sequelize.transaction(queryOptions).bind(this).then(function (transaction) {
}, { this.transaction = transaction;
transaction: options.transaction queryOptions.transaction = transaction;
return self.find(options, {
transaction: transaction,
lock: transaction.LOCK.UPDATE
}).then(function(instance) { }).then(function(instance) {
if (instance === null) { if (instance !== null) {
for (var attrname in defaults) { return [instance, false];
values[attrname] = defaults[attrname]; }
for (var attrname in options.defaults) {
values[attrname] = options.defaults[attrname];
} }
return self.create(values, options).then(function(instance) { return self.create(values, queryOptions).then(function(instance) {
return Promise.resolve([instance, true]); return [instance, true];
}).catch(self.sequelize.UniqueConstraintError, function () {
// Someone must have created a matching instance inside the same transaction since we last did a find. Let's find it!
// Postgres errors when there is a constraint violation inside a transaction, so we must first rollback to the savepoint we created before
return transaction.rollback().then(function () {
transaction = transaction.parent;
return self.find(options, {
transaction: transaction,
lock: transaction.LOCK.SHARE
}); });
}).then(function(instance) {
return [instance, false];
});
});
});
}).spread(function (instance, created) {
if (!internalTransaction) {
return Promise.resolve([instance, created]);
} }
return Promise.resolve([instance, false]); // If we created a transaction internally, we should clean it up
return this.transaction.commit().return([instance, created]);
}); });
}; };
......
...@@ -247,6 +247,12 @@ module.exports = (function() { ...@@ -247,6 +247,12 @@ module.exports = (function() {
Sequelize.prototype.ValidationErrorItem = Sequelize.ValidationErrorItem = Sequelize.prototype.ValidationErrorItem = Sequelize.ValidationErrorItem =
sequelizeErrors.ValidationErrorItem; sequelizeErrors.ValidationErrorItem;
Sequelize.prototype.DatabaseError = Sequelize.DatabaseError =
sequelizeErrors.DatabaseError;
Sequelize.prototype.UniqueConstraintError = Sequelize.UniqueConstraintError =
sequelizeErrors.UniqueConstraintError;
/** /**
* Returns the specified dialect. * Returns the specified dialect.
* *
......
...@@ -22,6 +22,7 @@ var Transaction = module.exports = function(sequelize, options) { ...@@ -22,6 +22,7 @@ var Transaction = module.exports = function(sequelize, options) {
this.id = this.options.transaction.id; this.id = this.options.transaction.id;
this.options.transaction.savepoints.push(this); this.options.transaction.savepoints.push(this);
this.name = this.id + '-savepoint-' + this.options.transaction.savepoints.length; this.name = this.id + '-savepoint-' + this.options.transaction.savepoints.length;
this.parent = this.options.transaction;
} else { } else {
this.id = this.name = Utils.generateUUID(); this.id = this.name = Utils.generateUUID();
} }
......
...@@ -312,7 +312,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -312,7 +312,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
it('allows us to customize the error message for unique constraint', function(done) { it('allows us to customize the error message for unique constraint', function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', { var self = this
, User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: { name: 'user_and_email', msg: 'User and email must be unique' }}, username: { type: Sequelize.STRING, unique: { name: 'user_and_email', msg: 'User and email must be unique' }},
email: { type: Sequelize.STRING, unique: 'user_and_email' }, email: { type: Sequelize.STRING, unique: 'user_and_email' },
aCol: { type: Sequelize.STRING, unique: 'a_and_b' }, aCol: { type: Sequelize.STRING, unique: 'a_and_b' },
...@@ -321,7 +322,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -321,7 +322,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
User.create({username: 'tobi', email: 'tobi@tobi.me'}).success(function() { User.create({username: 'tobi', email: 'tobi@tobi.me'}).success(function() {
User.create({username: 'tobi', email: 'tobi@tobi.me'}).error(function(err) { User.create({username: 'tobi', email: 'tobi@tobi.me'}).catch(self.sequelize.UniqueConstraintError, function(err) {
expect(err.message).to.equal('User and email must be unique') expect(err.message).to.equal('User and email must be unique')
done() done()
}) })
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* jshint expr: true */ /* jshint expr: true */
var chai = require('chai') var chai = require('chai')
, Sequelize = require('../../index') , Sequelize = require('../../index')
, Promise = Sequelize.Promise
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types") , DataTypes = require(__dirname + "/../../lib/data-types")
...@@ -40,7 +41,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -40,7 +41,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
.sync({ force: true }) .sync({ force: true })
.success(function() { .success(function() {
sequelize.transaction().then(function(t) { sequelize.transaction().then(function(t) {
User.findOrCreate({ username: 'Username' }, { data: 'some data' }, { transaction: t }).complete(function(err) { User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }}, { transaction: t }).complete(function(err) {
expect(err).to.be.null expect(err).to.be.null
User.count().success(function(count) { User.count().success(function(count) {
...@@ -65,9 +66,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -65,9 +66,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}; };
this.User.create(data).success(function (user) { this.User.create(data).success(function (user) {
self.User.findOrCreate({ self.User.findOrCreate({ where: {
username: user.username username: user.username
}).spread(function (_user, created) { }}).spread(function (_user, created) {
expect(_user.id).to.equal(user.id) expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username') expect(_user.username).to.equal('Username')
expect(created).to.be.false expect(created).to.be.false
...@@ -84,7 +85,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -84,7 +85,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}; };
this.User.create(data).success(function (user) { this.User.create(data).success(function (user) {
self.User.findOrCreate(data).done(function (err, _user, created) { self.User.findOrCreate({where: data}).done(function (err, _user, created) {
expect(_user.id).to.equal(user.id) expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username') expect(_user.username).to.equal('Username')
expect(_user.data).to.equal('ThisIsData') expect(_user.data).to.equal('ThisIsData')
...@@ -102,7 +103,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -102,7 +103,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
data: 'ThisIsData' data: 'ThisIsData'
}; };
this.User.findOrCreate(data, default_values).success(function(user, created) { this.User.findOrCreate({ where: data, defaults: default_values}).success(function(user, created) {
expect(user.username).to.equal('Username') expect(user.username).to.equal('Username')
expect(user.data).to.equal('ThisIsData') expect(user.data).to.equal('ThisIsData')
expect(created).to.be.true expect(created).to.be.true
...@@ -111,10 +112,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -111,10 +112,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
it("supports .or() (only using default values)", function (done) { it("supports .or() (only using default values)", function (done) {
this.User.findOrCreate( this.User.findOrCreate({
Sequelize.or({username: 'Fooobzz'}, {secretValue: 'Yolo'}), where: Sequelize.or({username: 'Fooobzz'}, {secretValue: 'Yolo'}),
{username: 'Fooobzz', secretValue: 'Yolo'} defaults: {username: 'Fooobzz', secretValue: 'Yolo'}
).done(function (err, user, created) { }).done(function (err, user, created) {
expect(err).not.to.be.ok expect(err).not.to.be.ok
expect(user.username).to.equal('Fooobzz') expect(user.username).to.equal('Fooobzz')
expect(user.secretValue).to.equal('Yolo') expect(user.secretValue).to.equal('Yolo')
...@@ -123,7 +124,48 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -123,7 +124,48 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
}) })
})
describe.only('several concurrent calls', function () {
beforeEach(function () {
var self = this;
return Support.prepareTransactionTest(this.sequelize, function(sequelize) {
self.sequelize = sequelize;
// self.User = sequelize.define('User', {
// username: DataTypes.STRING,
// });
});
});
it('works with a transaction', function () {
return this.sequelize.transaction().bind(this).then(function (transaction) {
return Promise.join(
this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }),
this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }),
function (first, second) {
console.log(first);
console.log(second);
// expect(first[1]).to.be.ok;
// expect(second[1]).not.to.be.ok;
return transaction.commit();
}
);
});
});
// it('works without a transaction', function () {
// return Promise.join(
// this.User.findOrCreate({ where: { uniqueName: 'winner' }}),
// this.User.findOrCreate({ where: { uniqueName: 'winner' }}),
// function (first, second) {
// expect(first[1]).to.be.ok;
// expect(second[1]).not.to.be.ok;
// }
// );
// });
});
});
describe('create', function() { describe('create', function() {
it('works with non-integer primary keys with a default value', function (done) { it('works with non-integer primary keys with a default value', function (done) {
...@@ -389,24 +431,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -389,24 +431,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
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 self = this
, User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true } username: { type: Sequelize.STRING, unique: true }
}) })
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
User.create({ username:'foo' }).success(function() { User.create({ username:'foo' }).success(function() {
User.create({ username: 'foo' }).error(function(err) { User.create({ username: 'foo' }).catch(self.sequelize.UniqueConstraintError, function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/.*Duplicate\ entry.*/)
} else {
expect(err.message).to.match(/.*duplicate\ key\ value.*/)
}
done() done()
}) })
}) })
...@@ -441,7 +473,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -441,7 +473,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it("raises an error if created object breaks definition contraints", function(done) { it("raises an error if created object breaks definition contraints", function(done) {
var UserNull = this.sequelize.define('UserWithNonNullSmth', { var self = this
, UserNull = this.sequelize.define('UserWithNonNullSmth', {
username: { type: Sequelize.STRING, unique: true }, username: { type: Sequelize.STRING, unique: true },
smth: { type: Sequelize.STRING, allowNull: false } smth: { type: Sequelize.STRING, allowNull: false }
}) })
...@@ -450,16 +483,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -450,16 +483,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
UserNull.sync({ force: true }).success(function() { UserNull.sync({ force: true }).success(function() {
UserNull.create({ username: 'foo', smth: 'foo' }).success(function() { UserNull.create({ username: 'foo', smth: 'foo' }).success(function() {
UserNull.create({ username: 'foo', smth: 'bar' }).error(function(err) { UserNull.create({ username: 'foo', smth: 'bar' }).catch(self.sequelize.UniqueConstraintError, function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/)
} else {
expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/)
}
done() done()
}) })
}) })
......
...@@ -26,7 +26,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -26,7 +26,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
describe('scopes', function() { describe.only('scopes', function() {
beforeEach(function() { beforeEach(function() {
this.ScopeMe = this.sequelize.define('ScopeMe', { this.ScopeMe = this.sequelize.define('ScopeMe', {
username: Sequelize.STRING, username: Sequelize.STRING,
...@@ -267,7 +267,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -267,7 +267,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}); });
it("should have no problem performing findOrCreate", function() { it("should have no problem performing findOrCreate", function() {
return this.ScopeMe.findOrCreate({username: 'fake'}).spread(function(user) { return this.ScopeMe.findOrCreate({ where: {username: 'fake'}}).spread(function(user) {
expect(user.username).to.equal('fake'); expect(user.username).to.equal('fake');
}); });
}); });
......
"use strict";
/* jshint camelcase: false */ /* jshint camelcase: false */
var chai = require('chai') var chai = require('chai')
, sinon = require('sinon')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, Sequelize = Support.Sequelize; , Sequelize = Support.Sequelize
, Promise = Sequelize.Promise;
chai.config.includeStack = true; chai.config.includeStack = true;
...@@ -49,7 +53,35 @@ describe(Support.getTestDialectTeaser("Sequelize Errors"), function () { ...@@ -49,7 +53,35 @@ describe(Support.getTestDialectTeaser("Sequelize Errors"), function () {
var matches = validationError.get('first_name'); var matches = validationError.get('first_name');
expect(matches).to.be.instanceOf(Array); expect(matches).to.be.instanceOf(Array);
expect(matches).to.have.lengthOf(1); expect(matches).to.have.lengthOf(1);
expect(matches[0]).to.have.property('message', 'invalid') expect(matches[0]).to.have.property('message', 'invalid');
});
});
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'
}
});
return this.sequelize.sync({ force: true }).bind(this).then(function () {
return User.create({ first_name: 'jan', last_name: 'meier' });
}).then(function () {
return Promise.join(
User.create({ first_name: 'jan', last_name: 'meier' }).catch(this.sequelize.UniqueConstraintError, spy),
User.create({ first_name: 'jan', last_name: 'meier' }).catch(this.sequelize.ConstraintError, spy),
function () {
expect(spy).to.have.been.calledTwice;
}
);
});
});
}); });
})
}); });
...@@ -321,7 +321,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -321,7 +321,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
describe('with spread', function () { describe('with spread', function () {
it('user not created', function (done) { it('user not created', function (done) {
this.User this.User
.findOrCreate({ id: 1}) .findOrCreate({ where: { id: 1}})
.spread(function(user, created) { .spread(function(user, created) {
expect(user.id).to.equal(1) expect(user.id).to.equal(1)
expect(created).to.equal(false) expect(created).to.equal(false)
...@@ -331,7 +331,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -331,7 +331,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
}) })
it('user created', function (done) { it('user created', function (done) {
this.User this.User
.findOrCreate({ id: 2}) .findOrCreate({ where: { id: 2}})
.spread(function(user, created) { .spread(function(user, created) {
expect(user.id).to.equal(2) expect(user.id).to.equal(2)
expect(created).to.equal(true) expect(created).to.equal(true)
......
...@@ -74,7 +74,7 @@ var Support = { ...@@ -74,7 +74,7 @@ var Support = {
var sequelizeOptions = _.defaults(options, { var sequelizeOptions = _.defaults(options, {
host: options.host || config.host, host: options.host || config.host,
logging: false, // logging: false,
dialect: options.dialect, dialect: options.dialect,
port: options.port || process.env.SEQ_PORT || config.port, port: options.port || process.env.SEQ_PORT || config.port,
pool: config.pool, pool: config.pool,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!