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

Commit 2302ec49 by Jan Aagaard Meier

More work in findorcreate with transaction

1 parent 4a4c976a
...@@ -19,6 +19,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell ...@@ -19,6 +19,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
+ moment 2.5.0 -> 2.7.0 + moment 2.5.0 -> 2.7.0
+ generic-pool 2.0.4 -> 2.1.1 + generic-pool 2.0.4 -> 2.1.1
+ sql 0.35.0 -> 0.39.0 + sql 0.35.0 -> 0.39.0
- [INTERNALS] Use a transaction inside `findOrCreate`, and handle unique constraint errors if multiple calls are issues concurrently on the same transaction
#### Backwards compatability changes #### Backwards compatability changes
- We are using a new inflection library, which should make pluralization and singularization in general more robust. However, a couple of pluralizations have changed as a result: - We are using a new inflection library, which should make pluralization and singularization in general more robust. However, a couple of pluralizations have changed as a result:
...@@ -35,6 +36,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell ...@@ -35,6 +36,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
Old: `err.validateCustom[0]` Old: `err.validateCustom[0]`
New: `err.get('validateCustom')[0]` New: `err.get('validateCustom')[0]`
- The syntax for findOrCreate has changed, to be more in line with the rest of the library. `Model.findOrCreate(where, defaults);` becomes `Model.findOrCreate({ where: where, defaults: defaults });`.
# v2.0.0-dev12 # v2.0.0-dev12
......
...@@ -45,12 +45,13 @@ var options = { ...@@ -45,12 +45,13 @@ var options = {
docfile.members = []; docfile.members = [];
docfile.javadoc.forEach(function(javadoc){ docfile.javadoc.forEach(function(javadoc){
// Find constructor tags
javadoc.isConstructor = getTag(javadoc.raw.tags, 'constructor') !== undefined; javadoc.isConstructor = getTag(javadoc.raw.tags, 'constructor') !== undefined;
javadoc.isMixin = getTag(javadoc.raw.tags, 'mixin') !== undefined; javadoc.isMixin = getTag(javadoc.raw.tags, 'mixin') !== undefined;
javadoc.isProperty = getTag(javadoc.raw.tags, 'property') !== undefined; javadoc.isProperty = getTag(javadoc.raw.tags, 'property') !== undefined;
javadoc.private = getTag(javadoc.raw.tags, 'private') !== undefined; javadoc.private = getTag(javadoc.raw.tags, 'private') !== undefined;
javadoc.since = getTag(javadoc.raw.tags, 'since'); javadoc.since = getTag(javadoc.raw.tags, 'since');
javadoc.extends = getTag(javadoc.raw.tags, 'extends');
// Find constructor tags
// Only show params without a dot in them (dots means attributes of object, so no need to clutter the signature too much) // Only show params without a dot in them (dots means attributes of object, so no need to clutter the signature too much)
var params = [] ; var params = [] ;
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<? }) -?> <? }) -?>
<? if (comment.paramTags.length > 0) { ?> <? if (comment.paramTags.length > 0) { ?>
##### Params: ##### Params:
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
<? comment.paramTags.forEach(function(paramTag) { -?> <? comment.paramTags.forEach(function(paramTag) { -?>
...@@ -66,6 +66,9 @@ ...@@ -66,6 +66,9 @@
<? if (comment.since) { ?> <? if (comment.since) { ?>
__Since:__ *<?= comment.since.string ?>* __Since:__ *<?= comment.since.string ?>*
<? } ?> <? } ?>
<? if (comment.extends) { ?>
__Extends:__ *<?= comment.extends.otherClass ?>*
<? } ?>
====== ======
<? } ?> <? } ?>
<? }) ?> <? }) ?>
...@@ -74,4 +77,4 @@ ...@@ -74,4 +77,4 @@
_This documentation was automagically created on <?= new Date().toString() ?>_ _This documentation was automagically created on <?= new Date().toString() ?>_
<? }) ?> <? }) ?>
\ No newline at end of file
...@@ -182,6 +182,12 @@ module.exports = (function() { ...@@ -182,6 +182,12 @@ module.exports = (function() {
emptyQuery += ' RETURNING *'; emptyQuery += ' RETURNING *';
} }
if (this._dialect.supports['EXCEPTION'] && options.exception) {
// Mostly for internal use, so we expect the user to know what he's doing!
// pg_temp functions are private per connection, so we never risk this function interfering with another one.
valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc() RETURNS SETOF <%= table %> AS $body$ BEGIN RETURN QUERY ' + valueQuery + '; EXCEPTION ' + options.exception + ' END; $body$ LANGUAGE plpgsql; SELECT * FROM pg_temp.testfunc()';
}
valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull); valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull);
for (key in valueHash) { for (key in valueHash) {
......
...@@ -138,6 +138,10 @@ module.exports = (function() { ...@@ -138,6 +138,10 @@ module.exports = (function() {
AbstractQuery.prototype.isInsertQuery = function(results, metaData) { AbstractQuery.prototype.isInsertQuery = function(results, metaData) {
var result = true; var result = true;
if (this.options.type === QueryTypes.INSERT) {
return true;
}
// is insert query if sql contains insert into // is insert query if sql contains insert into
result = result && (this.sql.toLowerCase().indexOf('insert into') === 0); result = result && (this.sql.toLowerCase().indexOf('insert into') === 0);
......
...@@ -14,6 +14,7 @@ var PostgresDialect = function(sequelize) { ...@@ -14,6 +14,7 @@ var PostgresDialect = function(sequelize) {
PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), { PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), {
'RETURNING': true, 'RETURNING': true,
'DEFAULT VALUES': true, 'DEFAULT VALUES': true,
'EXCEPTION': true,
schemas: true, schemas: true,
lock: true, lock: true,
forShare: 'FOR SHARE', forShare: 'FOR SHARE',
......
...@@ -198,6 +198,8 @@ module.exports = (function() { ...@@ -198,6 +198,8 @@ module.exports = (function() {
} }
return err; return err;
case 'SQLITE_BUSY':
return new sequelizeErrors.TimeoutError(err);
} }
return new sequelizeErrors.DatabaseError(err); return new sequelizeErrors.DatabaseError(err);
......
...@@ -33,6 +33,8 @@ util.inherits(error.BaseError, Error); ...@@ -33,6 +33,8 @@ util.inherits(error.BaseError, Error);
* *
* @param {string} message Error message * @param {string} message Error message
* @param {Array} [errors] Array of ValidationErrorItem objects describing the validation errors * @param {Array} [errors] Array of ValidationErrorItem objects describing the validation errors
*
* @extends BaseError
* @constructor * @constructor
*/ */
error.ValidationError = function(message, errors) { error.ValidationError = function(message, errors) {
...@@ -57,6 +59,18 @@ error.ValidationError.prototype.get = function(path) { ...@@ -57,6 +59,18 @@ error.ValidationError.prototype.get = function(path) {
}, []); }, []);
}; };
/**
* An array of ValidationErrorItems
* @property errors
* @name errors
*/
error.ValidationError.prototype.errors;
/**
* A base class for all database related errors.
* @extends BaseError
* @constructor
*/
error.DatabaseError = function (parent) { error.DatabaseError = function (parent) {
error.BaseError.apply(this, arguments); error.BaseError.apply(this, arguments);
this.name = 'SequelizeDatabaseError'; this.name = 'SequelizeDatabaseError';
...@@ -66,7 +80,39 @@ error.DatabaseError = function (parent) { ...@@ -66,7 +80,39 @@ error.DatabaseError = function (parent) {
}; };
util.inherits(error.DatabaseError, error.BaseError); util.inherits(error.DatabaseError, error.BaseError);
/**
* The database specific error which triggered this one
* @property parent
* @name parent
*/
error.DatabaseError.prototype.parent;
/**
* The SQL that triggered the error
* @property sql
* @name sql
*/
error.DatabaseError.prototype.sql;
/**
* Thrown when a database query times out because of a deadlock
* @extends DatabaseError
* @constructor
*/
error.TimeoutError = function (parent) {
error.DatabaseError.call(this, parent);
this.name = 'SequelizeTimeoutError';
};
util.inherits(error.TimeoutError, error.BaseError);
/**
* Thrown when a unique constraint is violated in the database
* @extends DatabaseError
* @constructor
*/
error.UniqueConstraintError = function (options) { error.UniqueConstraintError = function (options) {
options = options || {};
options.parent = options.parent || { sql: '' };
error.DatabaseError.call(this, options.parent); error.DatabaseError.call(this, options.parent);
this.name = 'SequelizeUniqueConstraintError'; this.name = 'SequelizeUniqueConstraintError';
...@@ -76,13 +122,30 @@ error.UniqueConstraintError = function (options) { ...@@ -76,13 +122,30 @@ error.UniqueConstraintError = function (options) {
this.index = options.index; this.index = options.index;
}; };
util.inherits(error.UniqueConstraintError, error.DatabaseError); util.inherits(error.UniqueConstraintError, error.DatabaseError);
/** /**
* An array of ValidationErrorItems * The message from the DB.
* @property errors * @property message
* @name errors * @name message
*/ */
error.ValidationError.prototype.errors; error.DatabaseError.prototype.message;
/**
* The fields of the unique constraint
* @property fields
* @name fields
*/
error.DatabaseError.prototype.fields;
/**
* The value(s) which triggered the error
* @property value
* @name value
*/
error.DatabaseError.prototype.value;
/**
* The name of the index that triggered the error
* @property index
* @name index
*/
error.DatabaseError.prototype.index;
/** /**
* Validation Error Item * Validation Error Item
......
...@@ -1077,6 +1077,10 @@ module.exports = (function() { ...@@ -1077,6 +1077,10 @@ 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()
* *
* If no transaction is passed in the `queryOptions` object, a new transaction will be created internally, to prevent the race condition where a matching row is created by another connection after the find but before the insert call.
* However, it is not always possible to handle this case in SQLite, specifically if one transaction inserts and another tries to select before the first one has comitted. In this case, an instance of sequelize.TimeoutError will be thrown instead.
* If a transaction is created, a savepoint will be created instead, and any unique constraint violation will be handled internally.
*
* @param {Object} [options] * @param {Object} [options]
* @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.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.defaults] Default values to use if creating a new instance * @param {Object} [options.defaults] Default values to use if creating a new instance
...@@ -1087,7 +1091,7 @@ module.exports = (function() { ...@@ -1087,7 +1091,7 @@ module.exports = (function() {
Model.prototype.findOrCreate = function(options, queryOptions) { Model.prototype.findOrCreate = function(options, queryOptions) {
var self = this var self = this
, internalTransaction = !(queryOptions && queryOptions.transaction) , internalTransaction = !(queryOptions && queryOptions.transaction)
, values = {}; , values;
queryOptions = queryOptions ? Utils._.clone(queryOptions) : {}; queryOptions = queryOptions ? Utils._.clone(queryOptions) : {};
...@@ -1095,53 +1099,50 @@ module.exports = (function() { ...@@ -1095,53 +1099,50 @@ module.exports = (function() {
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.)'); 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 (!options.where._isSequelizeMethod && !Array.isArray(options.where)) {
for (var attrname in options.where) {
values[attrname] = options.where[attrname];
}
}
// Create a transaction or a savepoint, depending on whether a transaction was passed in // Create a transaction or a savepoint, depending on whether a transaction was passed in
return self.sequelize.transaction(queryOptions).bind(this).then(function (transaction) { return self.sequelize.transaction(queryOptions).bind({}).then(function (transaction) {
this.transaction = transaction; this.transaction = transaction;
queryOptions.transaction = transaction; queryOptions.transaction = transaction;
return self.find(options, { return self.find(options, {
transaction: transaction, transaction: transaction,
lock: transaction.LOCK.UPDATE });
}).then(function(instance) { }).then(function(instance) {
if (instance !== null) { if (instance !== null) {
return [instance, false]; return [instance, false];
} }
for (var attrname in options.defaults) { values = Utils._.clone(options.defaults) || {};
values[attrname] = options.defaults[attrname]; if (Utils._.isPlainObject(options.where)) {
values = Utils._.defaults(values, options.where);
}
// VERY PG specific
// If a unique constraint is triggered inside a transaction, we cannot execute further queries inside that transaction, short of rolling back or committing.
// To circumwent this, we add an EXCEPTION WHEN unique_violation clause, which always returns an empty result set
queryOptions.exception = 'WHEN unique_violation THEN RETURN QUERY SELECT * FROM <%= table %> WHERE 1 <> 1;';
return self.create(values, queryOptions).bind(this).then(function(instance) {
console.log(this);
if (instance[self.primaryKeyAttribute] === null) {
// If the query returned an empty result for the primary key, we know that this was actually a unique constraint violation
throw new self.sequelize.UniqueConstraintError();
} }
return self.create(values, queryOptions).then(function(instance) { return [instance, true];
return [instance, true]; }).catch(self.sequelize.UniqueConstraintError, function () {
}).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!
// Someone must have created a matching instance inside the same transaction since we last did a find. Let's find it! return self.find(options, {
// Postgres errors when there is a constraint violation inside a transaction, so we must first rollback to the savepoint we created before transaction: this.transaction,
return transaction.rollback().then(function () { }).then(function(instance) {
transaction = transaction.parent; return [instance, false];
return self.find(options, {
transaction: transaction,
lock: transaction.LOCK.SHARE
});
}).then(function(instance) {
return [instance, false];
});
}); });
}); });
}).spread(function (instance, created) { }).tap(function () {
if (!internalTransaction) { if (internalTransaction) {
return Promise.resolve([instance, created]); // If we created a transaction internally, we should clean it up
return this.transaction.commit();
} }
// If we created a transaction internally, we should clean it up
return this.transaction.commit().return([instance, created]);
}); });
}; };
......
...@@ -434,6 +434,7 @@ module.exports = (function() { ...@@ -434,6 +434,7 @@ module.exports = (function() {
QueryInterface.prototype.insert = function(dao, tableName, values, options) { QueryInterface.prototype.insert = function(dao, tableName, values, options) {
var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes, options); var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes, options);
options.type = 'INSERT';
return this.sequelize.query(sql, dao, options).then(function(result) { return this.sequelize.query(sql, dao, options).then(function(result) {
result.isNewRecord = false; result.isNewRecord = false;
return result; return result;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module.exports = { module.exports = {
SELECT: 'SELECT', SELECT: 'SELECT',
INSERT: 'INSERT',
BULKUPDATE: 'BULKUPDATE', BULKUPDATE: 'BULKUPDATE',
BULKDELETE: 'BULKDELETE' BULKDELETE: 'BULKDELETE'
}; };
...@@ -247,9 +247,24 @@ module.exports = (function() { ...@@ -247,9 +247,24 @@ module.exports = (function() {
Sequelize.prototype.ValidationErrorItem = Sequelize.ValidationErrorItem = Sequelize.prototype.ValidationErrorItem = Sequelize.ValidationErrorItem =
sequelizeErrors.ValidationErrorItem; sequelizeErrors.ValidationErrorItem;
/**
* A base class for all database related errors.
* @see {Errors#DatabaseError}
*/
Sequelize.prototype.DatabaseError = Sequelize.DatabaseError = Sequelize.prototype.DatabaseError = Sequelize.DatabaseError =
sequelizeErrors.DatabaseError; sequelizeErrors.DatabaseError;
/**
* Thrown when a database query times out because of a deadlock
* @see {Errors#TimeoutError}
*/
Sequelize.prototype.TimeoutError = Sequelize.TimeoutError =
sequelizeErrors.TimeoutError;
/**
* Thrown when a unique constraint is violated in the database
* @see {Errors#UniqueConstraintError}
*/
Sequelize.prototype.UniqueConstraintError = Sequelize.UniqueConstraintError = Sequelize.prototype.UniqueConstraintError = Sequelize.UniqueConstraintError =
sequelizeErrors.UniqueConstraintError; sequelizeErrors.UniqueConstraintError;
......
...@@ -147,5 +147,6 @@ Transaction.prototype.setIsolationLevel = function() { ...@@ -147,5 +147,6 @@ Transaction.prototype.setIsolationLevel = function() {
}; };
Transaction.prototype.cleanup = function() { Transaction.prototype.cleanup = function() {
this.connection.uuid = undefined;
return this.sequelize.connectionManager.releaseConnection(this.connection); return this.sequelize.connectionManager.releaseConnection(this.connection);
}; };
...@@ -15,51 +15,43 @@ chai.use(datetime) ...@@ -15,51 +15,43 @@ chai.use(datetime)
chai.config.includeStack = true chai.config.includeStack = true
describe(Support.getTestDialectTeaser("DAOFactory"), function () { describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) { beforeEach(function () {
this.User = this.sequelize.define('User', { return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
username: DataTypes.STRING, this.sequelize = sequelize;
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN,
uniqueName: { type: DataTypes.STRING, unique: true }
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('findOrCreate', function () { this.User = this.sequelize.define('User', {
it("supports transactions", function(done) { username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN,
uniqueName: { type: DataTypes.STRING, unique: true }
})
Support.prepareTransactionTest(this.sequelize, function(sequelize) { return this.User.sync({ force: true });
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING, data: Sequelize.STRING }) });
});
User describe.only('findOrCreate', function () {
.sync({ force: true }) it("supports transactions", function(done) {
.success(function() { var self = this;
sequelize.transaction().then(function(t) { this.sequelize.transaction().then(function(t) {
User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }}, { transaction: t }).complete(function(err) { self.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }}, { transaction: t }).then(function() {
expect(err).to.be.null // self.User.count().success(function(count) {
// expect(count).to.equal(0)
User.count().success(function(count) { t.commit().success(function() {
expect(count).to.equal(0) self.User.count().success(function(count) {
t.commit().success(function() { expect(count).to.equal(1)
User.count().success(function(count) { done()
expect(count).to.equal(1) // })
done()
})
})
})
})
}) })
}) })
})
}) })
}) })
it("returns instance if already existent. Single find field.", function(done) { it.skip("returns instance if already existent. Single find field.", function(done) {
var self = this, var self = this,
data = { data = {
username: 'Username' username: 'Username'
...@@ -77,7 +69,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -77,7 +69,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it("Returns instance if already existent. Multiple find fields.", function(done) { it.skip("Returns instance if already existent. Multiple find fields.", function(done) {
var self = this, var self = this,
data = { data = {
username: 'Username', username: 'Username',
...@@ -95,7 +87,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -95,7 +87,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it("creates new instance with default value.", function(done) { it.skip("creates new instance with default value.", function(done) {
var data = { var data = {
username: 'Username' username: 'Username'
}, },
...@@ -111,7 +103,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -111,7 +103,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it("supports .or() (only using default values)", function (done) { it.skip("supports .or() (only using default values)", function (done) {
this.User.findOrCreate({ this.User.findOrCreate({
where: Sequelize.or({username: 'Fooobzz'}, {secretValue: 'Yolo'}), where: Sequelize.or({username: 'Fooobzz'}, {secretValue: 'Yolo'}),
defaults: {username: 'Fooobzz', secretValue: 'Yolo'} defaults: {username: 'Fooobzz', secretValue: 'Yolo'}
...@@ -125,45 +117,44 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -125,45 +117,44 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
describe.only('several concurrent calls', function () { describe('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 () { it('works with a transaction', function () {
return this.sequelize.transaction().bind(this).then(function (transaction) { return this.sequelize.transaction().bind(this).then(function (transaction) {
return Promise.join( return Promise.join(
this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }), this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }),
this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }), this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }),
function (first, second) { 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(); expect(first[0]).to.be.ok;
expect(first[1]).to.be.ok;
expect(second[0]).to.be.ok;
expect(second[1]).not.to.be.ok;
expect(first[0].id).to.equal(second[0].id);
return transaction.rollback();
} }
); );
}); });
}); });
// it('works without a transaction', function () { it('works without a transaction', function () {
// return Promise.join( var self = this;
// this.User.findOrCreate({ where: { uniqueName: 'winner' }}), if (dialect !== 'sqlite') { // Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off
// this.User.findOrCreate({ where: { uniqueName: 'winner' }}), return Promise.join(
// function (first, second) { this.User.findOrCreate({ where: { uniqueName: 'winner' }}),
// expect(first[1]).to.be.ok; this.User.findOrCreate({ where: { uniqueName: 'winner' }}),
// expect(second[1]).not.to.be.ok; function (first, second) {
// } expect(first[0]).to.be.ok;
// ); expect(first[1]).to.be.ok;
// }); expect(second[0]).to.be.ok;
expect(second[1]).not.to.be.ok;
expect(first[0].id).to.equal(second[0].id);
}
);
}
});
}); });
}); });
...@@ -220,21 +211,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -220,21 +211,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
it('supports transactions', function(done) { it('supports transactions', function(done) {
var self = this;
Support.prepareTransactionTest(this.sequelize, function(sequelize) { this.sequelize.transaction().then(function(t) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING }) self.User.create({ username: 'user' }, { transaction: t }).success(function() {
self.User.count().success(function(count) {
User.sync({ force: true }).success(function() { expect(count).to.equal(0)
sequelize.transaction().then(function(t) { t.commit().success(function() {
User.create({ username: 'user' }, { transaction: t }).success(function() { self.User.count().success(function(count) {
User.count().success(function(count) { expect(count).to.equal(1)
expect(count).to.equal(0) done()
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
})
}) })
}) })
}) })
...@@ -900,24 +885,19 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -900,24 +885,19 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe('bulkCreate', function() { describe('bulkCreate', function() {
it("supports transactions", function(done) { it("supports transactions", function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) { var self = this;
var User = sequelize.define('User', { username: Sequelize.STRING }) this.sequelize.transaction().then(function(t) {
self.User
User.sync({ force: true }).success(function() { .bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction: t })
sequelize.transaction().then(function(t) { .success(function() {
User self.User.count().success(function(count1) {
.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction: t }) self.User.count({ transaction: t }).success(function(count2) {
.success(function() { expect(count1).to.equal(0)
User.count().success(function(count1) { expect(count2).to.equal(2)
User.count({ transaction: t }).success(function(count2) { t.rollback().success(function(){ done() })
expect(count1).to.equal(0)
expect(count2).to.equal(2)
t.rollback().success(function(){ done() })
})
})
}) })
})
}) })
})
}) })
}) })
......
...@@ -14,19 +14,22 @@ chai.config.includeStack = true; ...@@ -14,19 +14,22 @@ chai.config.includeStack = true;
describe(Support.getTestDialectTeaser("DAOFactory"), function () { describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function() { beforeEach(function() {
this.User = this.sequelize.define('User', { return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
username: DataTypes.STRING, this.sequelize = sequelize;
secretValue: DataTypes.STRING, this.User = this.sequelize.define('User', {
data: DataTypes.STRING, username: DataTypes.STRING,
intVal: DataTypes.INTEGER, secretValue: DataTypes.STRING,
theDate: DataTypes.DATE, data: DataTypes.STRING,
aBool: DataTypes.BOOLEAN intVal: DataTypes.INTEGER,
}); theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
});
return this.User.sync({ force: true }); return this.User.sync({ force: true });
});
}); });
describe.only('scopes', function() { describe('scopes', function() {
beforeEach(function() { beforeEach(function() {
this.ScopeMe = this.sequelize.define('ScopeMe', { this.ScopeMe = this.sequelize.define('ScopeMe', {
username: Sequelize.STRING, username: Sequelize.STRING,
......
...@@ -76,9 +76,8 @@ describe(Support.getTestDialectTeaser("Sequelize Errors"), function () { ...@@ -76,9 +76,8 @@ describe(Support.getTestDialectTeaser("Sequelize Errors"), function () {
}).then(function () { }).then(function () {
return Promise.join( 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.UniqueConstraintError, spy),
User.create({ first_name: 'jan', last_name: 'meier' }).catch(this.sequelize.ConstraintError, spy),
function () { function () {
expect(spy).to.have.been.calledTwice; expect(spy).to.have.been.calledOnce;
} }
); );
}); });
......
...@@ -10,32 +10,35 @@ var chai = require('chai') ...@@ -10,32 +10,35 @@ var chai = require('chai')
chai.config.includeStack = true chai.config.includeStack = true
describe(Support.getTestDialectTeaser("Promise"), function () { describe(Support.getTestDialectTeaser("Promise"), function () {
beforeEach(function(done) { beforeEach(function() {
this.User = this.sequelize.define('User', { return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
username: { type: DataTypes.STRING }, this.sequelize = sequelize;
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, this.User = this.sequelize.define('User', {
aNumber: { type: DataTypes.INTEGER }, username: { type: DataTypes.STRING },
bNumber: { type: DataTypes.INTEGER }, touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
validateTest: { bNumber: { type: DataTypes.INTEGER },
type: DataTypes.INTEGER,
allowNull: true, validateTest: {
validate: {isInt: true} type: DataTypes.INTEGER,
}, allowNull: true,
validateCustom: { validate: {isInt: true}
type: DataTypes.STRING, },
allowNull: true, validateCustom: {
validate: {len: {msg: 'Length failed.', args: [1, 20]}} type: DataTypes.STRING,
}, allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
dateAllowNullTrue: { },
type: DataTypes.DATE,
allowNull: true dateAllowNullTrue: {
} type: DataTypes.DATE,
}) allowNull: true
}
})
this.User.sync({ force: true }).then(function() { done() }) return this.User.sync({ force: true })
}) })
});
describe('increment', function () { describe('increment', function () {
beforeEach(function(done) { beforeEach(function(done) {
......
...@@ -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,
...@@ -188,7 +188,7 @@ var Support = { ...@@ -188,7 +188,7 @@ var Support = {
}; };
var sequelize = Support.createSequelizeInstance(); var sequelize = Support.createSequelizeInstance();
//
// For Postgres' HSTORE functionality and to properly execute it's commands we'll need this... // For Postgres' HSTORE functionality and to properly execute it's commands we'll need this...
before(function() { before(function() {
var dialect = Support.getTestDialect(); var dialect = Support.getTestDialect();
...@@ -205,4 +205,5 @@ beforeEach(function() { ...@@ -205,4 +205,5 @@ beforeEach(function() {
return Support.clearDatabase(this.sequelize); return Support.clearDatabase(this.sequelize);
}); });
module.exports = Support; module.exports = Support;
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!