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

Commit d170b5da by Mick Hansen

Merge pull request #2990 from janmeier/rawquery

Revamped raw query handling
2 parents 52916fb8 67762ac6
......@@ -7,5 +7,6 @@ npm-debug.log
*~
test/binary/tmp/*
test/tmp/*
test/dialects/sqlite/test.sqlite
test/sqlite/test.sqlite
coverage-*
# Next
- [BUG] Fixed `field` support for `increment` and `decrement`.
- [FEATURE/BUG] Raw queries always return all results (including affected rows etc). This means you should change all promise listeners on `sequelize.query` to use `.spread` instead of `.then`, unless you are passing a query type.
#### Backwards compatability changes
- The default query type for `sequelize.query` is now `RAW` - this means that two arguments (results and metadata) will be returned by default and you should use `.spread`
- The 4th argument to `sequelize.query` has been deprecated in favor of `options.replacements`
# 2.0.0-rc8
- [FEATURE] CLS Support. CLS is also used to automatically pass the transaction to any calls within the callback chain when using `sequelize.transaction(function() ...`.
......
As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can utilize the function `sequelize.query`.
By default the function will return two arguments - a results array, and an object containing metadata (affected rows etc.). Note that since this is a raw query, the metadata (property names etc.) is dialect specific. Some dialects return the metadata "within" the results object (as properties on an array). However, two arguments will always be returned, but for MSSQL and MySQL it will be two references to the same object.
```js
sequelize.query("UPDATE users SET y = 42 WHERE x = 12").spread(function(results, metadata) {
// Results will be an empty array and metadata will contain the number of affected rows.
})
```
In cases where you don't need to access the metadata you can pass in a query type to tell sequelize how to format the results. For example, for a simple select query you could do:
```js
sequelize.query("SELECT * FROM `users`", { type: sequelize.QueryTypes.SELECT})
.then(function(users) {
// We don't need spread here, since only the results will be returned for select queries
})
```
Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/lib/query-types.js)
A second, optional, argument is the _callee_, which is a model. If you pass a model the returned data will be instances of that model.
```js
// Callee is the model definition. This allows you to easily map a query to a predefined model
sequelize.query('SELECT * FROM projects', Projects).then(function(projects){
// Each record will now be a instance of Project
})
```
# Replacements
Replacements in a query can be done in two different ways, either using named parameters (starting with `:`), or unnamed, represented by a `?`. Replacements are passed in the options object.
* If an array is passed, `?` will be replaced in the order that they appear in the array
* If an object is passed, `:key` will be replaced with the keys from that object. If the object contains keys not found in the query or vice verca, an exception will be thrown.
```js
sequelize.query('SELECT * FROM projects WHERE status = ?',
{ replacements: ['active'], type: sequelize.QueryTypes.SELECT }
).then(function(projects) {
console.log(projects)
})
sequelize.query('SELECT * FROM projects WHERE status = :status ',
{ replacements: { status: 'active', type: sequelize.QueryTypes.SELECT }}
).then(function(projects) {
console.log(projects)
})
```
......@@ -225,9 +225,9 @@ var sequelize = new Sequelize('database', 'username', 'password', {
## Executing raw SQL queries
As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can utilize the function`sequelize.query`.
As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can utilize the function `sequelize.query`.
Here is how it works:
Here is how it works:
```js
// Arguments for raw queries
......@@ -324,4 +324,4 @@ sequelize.query('select 1 as `foo.bar.baz`').success(function(rows) {
[0]: /docs/latest/usage#options
\ No newline at end of file
[0]: /docs/latest/usage#options
......@@ -83,6 +83,10 @@ module.exports = (function() {
}
};
AbstractQuery.prototype.isRawQuery = function () {
return this.options.type === QueryTypes.RAW;
};
AbstractQuery.prototype.isVersionQuery = function () {
return this.options.type === QueryTypes.VERSION;
};
......@@ -137,6 +141,10 @@ module.exports = (function() {
return this.options.type === QueryTypes.SHOWINDEXES;
};
AbstractQuery.prototype.isDescribeQuery = function () {
return this.options.type === QueryTypes.DESCRIBE;
};
AbstractQuery.prototype.isSelectQuery = function() {
return this.options.type === QueryTypes.SELECT;
};
......@@ -149,7 +157,15 @@ module.exports = (function() {
return this.options.type === QueryTypes.BULKDELETE;
};
AbstractQuery.prototype.isForeignKeysQuery = function() {
return this.options.type === QueryTypes.FOREIGNKEYS;
};
AbstractQuery.prototype.isUpdateQuery = function() {
if (this.options.type === QueryTypes.UPDATE) {
return true;
}
return (this.sql.toLowerCase().indexOf('update') === 0);
};
......@@ -167,15 +183,12 @@ module.exports = (function() {
}
}
return o;
});
if (this.options.nest) {
result = result.map(function(entry){
return Dot.transform(entry);
});
}
if (this.options.nest) {
o = Dot.transform(o);
}
return o;
}, this);
// Queries with include
} else if (this.options.hasJoin === true) {
results = groupJoinData(results, {
......
......@@ -129,23 +129,20 @@ module.exports = (function() {
if (this.isShowTablesQuery()) {
result = this.handleShowTablesQuery(data);
} else if (this.isShowOrDescribeQuery()) {
result = data;
if (this.sql.toLowerCase().indexOf("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'") === 0) {
result = {};
data.forEach(function(_result) {
if (_result.Default)
_result.Default = _result.Default.replace('(\'','').replace('\')','').replace(/'/g,'');
result[_result.Name] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.IsNull === 'YES' ? true : false),
defaultValue: _result.Default
};
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
}
} else if (this.isDescribeQuery()) {
result = {};
data.forEach(function(_result) {
if (_result.Default)
_result.Default = _result.Default.replace('(\'','').replace('\')','').replace(/'/g,'');
result[_result.Name] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.IsNull === 'YES' ? true : false),
defaultValue: _result.Default
};
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
} else if (this.isSelectQuery()) {
result = this.handleSelectQuery(data);
} else if (this.isCallQuery()) {
......@@ -156,7 +153,12 @@ module.exports = (function() {
result = data[0] && data[0].AFFECTEDROWS;
} else if (this.isVersionQuery()) {
result = data[0].version;
}
} else if (this.isForeignKeysQuery()) {
result = data;
} else if (this.isRawQuery()) {
// MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data];
}
return result;
};
......
......@@ -76,28 +76,30 @@ module.exports = (function() {
result = this.handleSelectQuery(data);
} else if (this.isShowTablesQuery()) {
result = this.handleShowTablesQuery(data);
} else if (this.isShowOrDescribeQuery()) {
result = data;
if (this.sql.toLowerCase().indexOf('describe') === 0) {
result = {};
} else if (this.isDescribeQuery()) {
result = {};
data.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default
};
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
data.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default
};
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
}
} else if (this.isCallQuery()) {
result = data[0];
} else if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) {
result = data.affectedRows;
} else if (this.isVersionQuery()) {
result = data[0].version;
} else if (this.isForeignKeysQuery()) {
result = data;
} else if (this.isRawQuery()) {
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data];
}
return result;
......
......@@ -869,8 +869,6 @@ module.exports = (function() {
return dataType;
},
quoteIdentifier: function(identifier, force) {
var _ = Utils._;
if (identifier === '*') return identifier;
......
......@@ -147,62 +147,60 @@ module.exports = (function() {
});
return result;
} else if (self.isSelectQuery()) {
if (self.sql.toLowerCase().indexOf('select c.column_name') === 0) {
result = {};
rows.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
};
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(self.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key];
if (targetAttr !== key) {
row[targetAttr] = row[key];
delete row[key];
}
});
});
}
if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];
if (!!self.callee && !!self.callee._hasHstoreAttributes) {
rows.forEach(function(row) {
parseHstoreFields(self.callee, row);
});
}
if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null;
}
return self.handleSelectQuery(rows);
} else if (QueryTypes.DESCRIBE === self.options.type) {
result = {};
rows.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
};
if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];
if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null;
}
}
if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');
if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');
if (result[_result.Field].defaultValue.indexOf('::') > -1) {
var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') {
result[_result.Field].defaultValue = split[0];
}
if (result[_result.Field].defaultValue.indexOf('::') > -1) {
var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') {
result[_result.Field].defaultValue = split[0];
}
}
});
return result;
} else {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(self.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key];
if (targetAttr !== key) {
row[targetAttr] = row[key];
delete row[key];
}
});
});
}
if (!!self.callee && !!self.callee._hasHstoreAttributes) {
rows.forEach(function(row) {
parseHstoreFields(self.callee, row);
});
}
});
return self.handleSelectQuery(rows);
}
return result;
} else if (self.isShowOrDescribeQuery()) {
return results;
} else if (QueryTypes.BULKUPDATE === self.options.type) {
......@@ -243,6 +241,8 @@ module.exports = (function() {
return self.callee || (rows && ((self.options.plain && rows[0]) || rows)) || undefined;
} else if (self.isVersionQuery()) {
return results[0].version;
} else if (self.isRawQuery()) {
return [rows, result];
} else {
return results;
}
......
......@@ -139,6 +139,8 @@ module.exports = (function() {
result = undefined;
} else if (self.options.type === QueryTypes.VERSION) {
result = results[0].version;
} else if (self.options.type === QueryTypes.RAW) {
result = [results, metaData];
}
resolve(result);
......
......@@ -831,7 +831,7 @@ module.exports = (function() {
* @param {Integer} [options.by=1] The number to increment by
* @param {Transaction} [options.transaction]
*
* @return {Promise}
* @return {Promise<this>}
*/
Instance.prototype.increment = function(fields, countOrOptions) {
Utils.validateParameter(countOrOptions, Object, {
......@@ -895,7 +895,7 @@ module.exports = (function() {
}
}, this);
return this.QueryInterface.increment(this, this.Model.getTableName(countOrOptions), values, where, countOrOptions);
return this.QueryInterface.increment(this, this.Model.getTableName(countOrOptions), values, where, countOrOptions).return(this);
};
/**
......
......@@ -725,7 +725,6 @@ module.exports = (function() {
}
}).then(function() {
return this.QueryInterface.select(this, this.getTableName(options), options, Utils._.defaults({
type: QueryTypes.SELECT,
hasJoin: hasJoin,
tableNames: Object.keys(tableNames)
}, queryOptions, { transaction: options.transaction }));
......@@ -743,9 +742,7 @@ module.exports = (function() {
// whereCollection is used for non-primary key updates
this.options.whereCollection = options.where || null;
return this.QueryInterface.select(this, [[this.getTableName(options), this.name], joinTableName], options, Utils._.defaults({
type: QueryTypes.SELECT
}, queryOptions, { transaction: (options || {}).transaction }));
return this.QueryInterface.select(this, [[this.getTableName(options), this.name], joinTableName], options, Utils._.defaults(queryOptions, { transaction: (options || {}).transaction }));
};
/**
......@@ -1449,7 +1446,6 @@ module.exports = (function() {
options.type = QueryTypes.BULKDELETE;
mapFieldNames.call(this, options, this);
return Promise.try(function() {
......
......@@ -46,12 +46,13 @@ module.exports = (function() {
var self = this;
options = Utils._.extend({
raw: true
raw: true,
type: this.sequelize.QueryTypes.SELECT
}, options || {});
var showSchemasSql = self.QueryGenerator.showSchemasQuery();
return this.sequelize.query(showSchemasSql, null, options).then(function(schemaNames) {
return this.sequelize.query(showSchemasSql, options).then(function(schemaNames) {
return Utils._.flatten(
Utils._.map(schemaNames, function(value) {
return (!!value.schema_name ? value.schema_name : value);
......@@ -83,7 +84,7 @@ module.exports = (function() {
});
options = Utils._.extend({
logging: this.sequelize.options.logging
logging: this.sequelize.options.logging,
}, options || {});
// Postgres requires a special SQL command for enums
......@@ -317,7 +318,7 @@ module.exports = (function() {
var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
return this.sequelize.query(sql, null, { raw: true }).then(function(data) {
return this.sequelize.query(sql, { type: QueryTypes.DESCRIBE }).then(function(data) {
// If no data is returned from the query, then the table name may be wrong.
// Query generators that use information_schema for retrieving table info will just return an empty result set,
// it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
......@@ -443,7 +444,7 @@ module.exports = (function() {
}
return Utils.Promise.map(tableNames, function(tableName) {
return self.sequelize.query(self.QueryGenerator.getForeignKeysQuery(tableName, self.sequelize.config.database));
return self.sequelize.query(self.QueryGenerator.getForeignKeysQuery(tableName, self.sequelize.config.database)).get(0);
}).then(function(results) {
var result = {};
......@@ -556,6 +557,9 @@ module.exports = (function() {
, restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options, dao.Model.rawAttributes);
options = options || {};
options.type = QueryTypes.UPDATE;
// Check for a restrict field
if (!!dao.Model && !!dao.Model.associations) {
var keys = Object.keys(dao.Model.associations)
......@@ -689,13 +693,14 @@ module.exports = (function() {
var sql = this.QueryGenerator.selectQuery(tableName, options, model);
queryOptions = Utils._.extend({}, queryOptions, {
type: QueryTypes.SELECT,
include: options.include,
includeNames: options.includeNames,
includeMap: options.includeMap,
hasSingleAssociation: options.hasSingleAssociation,
hasMultiAssociation: options.hasMultiAssociation,
attributes: options.attributes,
originalAttributes: options.originalAttributes
originalAttributes: options.originalAttributes,
});
return this.sequelize.query(sql, model, queryOptions);
......
......@@ -3,10 +3,14 @@
module.exports = {
SELECT: 'SELECT',
INSERT: 'INSERT',
UPDATE: 'UPDATE',
BULKUPDATE: 'BULKUPDATE',
BULKDELETE: 'BULKDELETE',
UPSERT: 'UPSERT',
VERSION: 'VERSION',
SHOWTABLES: 'SHOWTABLES',
SHOWINDEXES: 'SHOWINDEXES'
SHOWINDEXES: 'SHOWINDEXES',
DESCRIBE: 'DESCRIBE',
RAW: 'RAW',
FOREIGNKEYS: 'FOREIGNKEYS',
};
......@@ -227,7 +227,11 @@ module.exports = (function() {
*/
Sequelize.prototype.Promise = Sequelize.Promise = Promise;
Sequelize.QueryTypes = QueryTypes;
/**
* Available query types for use with `sequelize.query`
* @property QueryTypes
*/
Sequelize.prototype.QueryTypes = Sequelize.QueryTypes = QueryTypes;
/**
* Exposes the validator.js object, so you can extend it with custom validation functions. The validator is exposed both on the instance, and on the constructor.
......@@ -624,17 +628,31 @@ module.exports = (function() {
/**
* Execute a query on the DB, with the posibility to bypass all the sequelize goodness.
*
* If you do not provide other arguments than the SQL, raw will be assumed to the true, and sequelize will not try to do any formatting to the results of the query.
* By default, the function will return two arguments: an array of results, and a metadata object, containing number of affected rows etc. Use `.spread` to access the results.
*
* If you are running a type of query where you don't need the metadata, for example a `SELECT` query, you can pass in a query type to make sequelize format the results:
*
* ```js
* sequlize.query('SELECT...').spread(function (results, metadata) {
* // Raw query - use spread
* });
*
* sequlize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }).then(function (results) {
* // SELECT query - use then
* })
* ```
*
* @method query
* @param {String} sql
* @param {Instance} [callee] If callee is provided, the returned data will be put into the callee
* @param {Object} [options={}] Query options.
* @param {Boolean} [options.raw] If true, sequelize will not try to format the results of the query, or build an instance of a model from the result
* @param {Transaction} [options.transaction] The transaction that the query should be executed under
* @param {String} [options.type='SELECT'] The type of query you are executing. The query type affects how results are formatted before they are passed back. If no type is provided sequelize will try to guess the right type based on the sql, and fall back to SELECT. The type is a string, but `Sequelize.QueryTypes` is provided is convenience shortcuts. Current options are SELECT, BULKUPDATE and BULKDELETE
* @param {Boolean} [options.nest=false] If true, transforms objects with `.` separated property names into nested objects using [dottie.js](https://github.com/mickhansen/dottie.js). For example { 'user.username': 'john' } becomes { user: { username: 'john' }}
* @param {Object|Array} [replacements] Either an object of named parameter replacements in the format `:param` or an array of unnamed replacements to replace `?` in your SQL.
* @param {String} sql
* @param {Instance|Model} [callee] If callee is provided, the returned data will be put into the callee
* @param {Object} [options={}] Query options.
* @param {Boolean} [options.raw] If true, sequelize will not try to format the results of the query, or build an instance of a model from the result
* @param {Transaction} [options.transaction=null] The transaction that the query should be executed under
* @param {String} [options.type='RAW'] The type of query you are executing. The query type affects how results are formatted before they are passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts.
* @param {Boolean} [options.nest=false] If true, transforms objects with `.` separated property names into nested objects using [dottie.js](https://github.com/mickhansen/dottie.js). For example { 'user.username': 'john' } becomes { user: { username: 'john' }}. When `nest` is true, the query type is assumed to be `'SELECT'`, unless otherwise specified
* @param {Boolean} [options.plain=false] Sets the query type to `SELECT` and return a single row
* @param {Object|Array} [options.replacements] Either an object of named parameter replacements in the format `:param` or an array of unnamed replacements to replace `?` in your SQL.
*
* @return {Promise}
*
* @see {Model#build} for more information about callee.
......@@ -643,30 +661,52 @@ module.exports = (function() {
var self = this;
sql = sql.trim();
if (arguments.length === 4) {
if (Array.isArray(replacements)) {
sql = Utils.format([sql].concat(replacements), this.options.dialect);
}
else {
sql = Utils.formatNamedParameters(sql, replacements, this.options.dialect);
}
deprecated('passing raw query replacements as the 4th argument to sequelize.query is deprecated. Please use options.replacements instead');
options.replacements = replacements;
} else if (arguments.length === 3) {
options = options;
} else if (arguments.length === 2) {
options = {};
if (callee instanceof Sequelize.Model) {
options = {};
} else {
options = callee;
callee = undefined;
}
} else {
options = { raw: true };
}
if (!(callee instanceof Sequelize.Model)) {
// When callee is not set, assume raw query
options.raw = true;
}
if (options.replacements) {
if (Array.isArray(options.replacements)) {
sql = Utils.format([sql].concat(options.replacements), this.options.dialect);
}
else {
sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect);
}
}
options = Utils._.extend(Utils._.clone(this.options.query), options);
options = Utils._.defaults(options, {
logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log,
type: (sql.toLowerCase().indexOf('select') === 0) ? QueryTypes.SELECT : false
logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log
});
if (options.transaction === undefined && Sequelize.cls) {
options.transaction = Sequelize.cls.get('transaction');
}
if (!options.type) {
if (options.nest || options.plain) {
options.type = QueryTypes.SELECT;
} else {
options.type = QueryTypes.RAW;
}
}
if (options.transaction && options.transaction.finished) {
return Promise.reject(options.transaction.finished+' has been called on this transaction, you can no longer use it');
}
......
......@@ -19,6 +19,7 @@ pages:
- ['docs/hooks.md', 'Documentation', 'Hooks']
- ['docs/transactions.md', 'Documentation', 'Transactions']
- ['docs/legacy.md', 'Documentation', 'Working with legacy tables']
- ['docs/raw-queries.md', 'Documentation', 'Raw queries']
- ['docs/migrations.md', 'Documentation', 'Migrations']
- ['api/sequelize.md', 'API', 'Sequelize']
......@@ -29,4 +30,4 @@ pages:
- ['api/promise.md', 'API', 'Promise']
- ['api/transaction.md', 'API', 'Transaction']
- ['api/datatypes.md', 'API', 'Datatypes']
- ['api/errors.md', 'API', 'Errors']
\ No newline at end of file
- ['api/errors.md', 'API', 'Errors']
......@@ -34,24 +34,21 @@ if (Support.dialectIsMySQL()) {
});
});
it('accepts new queries after shutting down a connection', function(done) {
it('accepts new queries after shutting down a connection', function() {
// Create a sequelize instance with pooling disabled
var sequelize = Support.createSequelizeInstance({ pool: false });
var User = sequelize.define('User', { username: DataTypes.STRING });
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
// After 100 ms the DB connection will be disconnected for inactivity
setTimeout(function() {
// This query will be queued just after the `client.end` is executed and before its callback is called
sequelize.query('SELECT COUNT(*) AS count FROM Users').on('success', function(count) {
expect(count[0].count).to.equal(1);
done();
}).error(function(error) {
expect(error).to.not.exist;
});
}, 100);
});
return User.sync({force: true}).then(function() {
return User.create({username: 'user1'});
}).then(function() {
// After 100 ms the DB connection will be disconnected for inactivity
return sequelize.Promise.delay(100);
}).then(function () {
// This query will be queued just after the `client.end` is executed and before its callback is called
return sequelize.query('SELECT COUNT(*) AS count FROM Users', { type: sequelize.QueryTypes.SELECT });
}).then(function(count) {
expect(count[0].count).to.equal(1);
});
});
......
......@@ -18,7 +18,7 @@ chai.use(datetime);
chai.config.includeStack = true;
describe(Support.getTestDialectTeaser('Model'), function() {
beforeEach(function(done) {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
......@@ -28,25 +28,21 @@ describe(Support.getTestDialectTeaser('Model'), function() {
aBool: DataTypes.BOOLEAN
});
this.User.sync({ force: true }).success(function() {
done();
});
return this.User.sync({ force: true });
});
describe('constructor', function() {
it('uses the passed dao name as tablename if freezeTableName', function(done) {
it('uses the passed dao name as tablename if freezeTableName', function() {
var User = this.sequelize.define('FrozenUser', {}, { freezeTableName: true });
expect(User.tableName).to.equal('FrozenUser');
done();
});
it('uses the pluralized dao name as tablename unless freezeTableName', function(done) {
it('uses the pluralized dao name as tablename unless freezeTableName', function() {
var User = this.sequelize.define('SuperUser', {}, { freezeTableName: false });
expect(User.tableName).to.equal('SuperUsers');
done();
});
it('uses checks to make sure dao factory isnt leaking on multiple define', function(done) {
it('uses checks to make sure dao factory isnt leaking on multiple define', function() {
this.sequelize.define('SuperUser', {}, { freezeTableName: false });
var factorySize = this.sequelize.daoFactoryManager.all.length;
......@@ -54,10 +50,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
var factorySize2 = this.sequelize.daoFactoryManager.all.length;
expect(factorySize).to.equal(factorySize2);
done();
});
it('attaches class and instance methods', function(done) {
it('attaches class and instance methods', function() {
var User = this.sequelize.define('UserWithClassAndInstanceMethods', {}, {
classMethods: { doSmth: function() { return 1; } },
instanceMethods: { makeItSo: function() { return 2; } }
......@@ -70,10 +65,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
expect(User.build().doSmth).not.to.exist;
expect(User.build().makeItSo).to.exist;
expect(User.build().makeItSo()).to.equal(2);
done();
});
it('allows us us to predefine the ID column with our own specs', function(done) {
it('allows us us to predefine the ID column with our own specs', function() {
var User = this.sequelize.define('UserCol', {
id: {
type: Sequelize.STRING,
......@@ -82,15 +76,12 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}
});
User.sync({ force: true }).success(function() {
User.create({id: 'My own ID!'}).success(function(user) {
expect(user.id).to.equal('My own ID!');
done();
});
return User.sync({ force: true }).then(function() {
return expect(User.create({id: 'My own ID!'})).to.eventually.have.property('id', 'My own ID!');
});
});
it('throws an error if 2 autoIncrements are passed', function(done) {
it('throws an error if 2 autoIncrements are passed', function() {
var self = this;
expect(function() {
self.sequelize.define('UserWithTwoAutoIncrements', {
......@@ -98,10 +89,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
userscore: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
});
}).to.throw(Error, 'Invalid Instance definition. Only one autoincrement field allowed.');
done();
});
it('throws an error if a custom model-wide validation is not a function', function(done) {
it('throws an error if a custom model-wide validation is not a function', function() {
var self = this;
expect(function() {
self.sequelize.define('Foo', {
......@@ -112,10 +102,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}
});
}).to.throw(Error, 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction');
done();
});
it('throws an error if a custom model-wide validation has the same name as a field', function(done) {
it('throws an error if a custom model-wide validation has the same name as a field', function() {
var self = this;
expect(function() {
self.sequelize.define('Foo', {
......@@ -126,7 +115,6 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}
});
}).to.throw(Error, 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field');
done();
});
it('should allow me to set a default value for createdAt and updatedAt', function(done) {
......@@ -1235,8 +1223,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
});
});
it('sets deletedAt to the current timestamp if paranoid is true', function(done) {
it('sets deletedAt to the current timestamp if paranoid is true', function() {
var self = this
, qi = this.sequelize.queryInterface.QueryGenerator.quoteIdentifier.bind(this.sequelize.queryInterface.QueryGenerator)
, ParanoidUser = self.sequelize.define('ParanoidUser', {
username: Sequelize.STRING,
secretValue: Sequelize.STRING,
......@@ -1249,26 +1238,25 @@ describe(Support.getTestDialectTeaser('Model'), function() {
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }];
ParanoidUser.sync({ force: true }).success(function() {
ParanoidUser.bulkCreate(data).success(function() {
// since we save in UTC, let's format to UTC time
var date = moment().utc().format('YYYY-MM-DD h:mm');
ParanoidUser.destroy({where: {secretValue: '42'}}).success(function() {
ParanoidUser.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(1);
expect(users[0].username).to.equal('Bob');
return ParanoidUser.sync({ force: true }).then(function() {
return ParanoidUser.bulkCreate(data);
}).bind({}).then(function() {
// since we save in UTC, let's format to UTC time
this.date = moment().utc().format('YYYY-MM-DD h:mm');
return ParanoidUser.destroy({where: {secretValue: '42'}});
}).then(function() {
return ParanoidUser.findAll({order: 'id'});
}).then(function(users) {
expect(users.length).to.equal(1);
expect(users[0].username).to.equal('Bob');
self.sequelize.query('SELECT * FROM ' + self.sequelize.queryInterface.QueryGenerator.quoteIdentifier('ParanoidUsers') + ' WHERE ' + self.sequelize.queryInterface.QueryGenerator.quoteIdentifier('deletedAt') + ' IS NOT NULL ORDER BY ' + self.sequelize.queryInterface.QueryGenerator.quoteIdentifier('id'), null, {raw: true}).success(function(users) {
expect(users[0].username).to.equal('Peter');
expect(users[1].username).to.equal('Paul');
return self.sequelize.query('SELECT * FROM ' + qi('ParanoidUsers') + ' WHERE ' + qi('deletedAt') + ' IS NOT NULL ORDER BY ' + qi('id'));
}).spread(function(users) {
expect(users[0].username).to.equal('Peter');
expect(users[1].username).to.equal('Paul');
expect(moment(new Date(users[0].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(date);
expect(moment(new Date(users[1].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(date);
done();
});
});
});
});
expect(moment(new Date(users[0].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(this.date);
expect(moment(new Date(users[1].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(this.date);
});
});
......@@ -1377,46 +1365,44 @@ describe(Support.getTestDialectTeaser('Model'), function() {
});
});
it('should delete a paranoid record if I set force to true', function(done) {
it('should delete a paranoid record if I set force to true', function() {
var self = this;
var User = this.sequelize.define('paranoiduser', {
username: Sequelize.STRING
}, { paranoid: true });
User.sync({ force: true }).success(function() {
User.bulkCreate([
return User.sync({ force: true }).then(function() {
return User.bulkCreate([
{username: 'Bob'},
{username: 'Tobi'},
{username: 'Max'},
{username: 'Tony'}
]).success(function() {
User.find({where: {username: 'Bob'}}).success(function(user) {
user.destroy({force: true}).success(function() {
User.find({where: {username: 'Bob'}}).success(function(user) {
expect(user).to.be.null;
User.find({where: {username: 'Tobi'}}).success(function(tobi) {
tobi.destroy().success(function() {
self.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', null, {raw: true, plain: true}).success(function(result) {
expect(result.username).to.equal('Tobi');
User.destroy({where: {username: 'Tony'}}).success(function() {
self.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', null, {raw: true, plain: true}).success(function(result) {
expect(result.username).to.equal('Tony');
User.destroy({where: {username: ['Tony', 'Max']}, force: true}).success(function() {
self.sequelize.query('SELECT * FROM paranoidusers', null, {raw: true}).success(function(users) {
expect(users).to.have.length(1);
expect(users[0].username).to.equal('Tobi');
done();
});
});
});
});
});
});
});
});
});
});
});
]);
}).then(function() {
return User.find({where: {username: 'Bob'}});
}).then(function(user) {
return user.destroy({force: true});
}).then(function() {
return expect(User.find({where: {username: 'Bob'}})).to.eventually.be.null;
}).then(function(user) {
return User.find({where: {username: 'Tobi'}});
}).then(function(tobi) {
return tobi.destroy();
}).then(function() {
return self.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', { plain: true});
}).then(function(result) {
expect(result.username).to.equal('Tobi');
return User.destroy({where: {username: 'Tony'}});
}).then(function() {
return self.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', { plain: true});
}).then(function(result) {
expect(result.username).to.equal('Tony');
return User.destroy({where: {username: ['Tony', 'Max']}, force: true});
}).then(function() {
return self.sequelize.query('SELECT * FROM paranoidusers', null, {raw: true});
}).spread(function(users) {
expect(users).to.have.length(1);
expect(users[0].username).to.equal('Tobi');
});
});
......
......@@ -424,15 +424,15 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
});
});
it('should get a list of foreign keys for the table', function(done) {
var sql =
this.queryInterface.QueryGenerator.getForeignKeysQuery('hosts', this.sequelize.config.database);
this.sequelize.query(sql).complete(function(err, fks) {
expect(err).to.be.null;
it('should get a list of foreign keys for the table', function() {
var sql = this.queryInterface.QueryGenerator.getForeignKeysQuery('hosts', this.sequelize.config.database);
return this.sequelize.query(sql, {type: this.sequelize.QueryTypes.FOREIGNKEYS}).then(function(fks) {
expect(fks).to.have.length(3);
var keys = Object.keys(fks[0]),
keys2 = Object.keys(fks[1]),
keys3 = Object.keys(fks[2]);
if (dialect === 'postgres' || dialect === 'postgres-native') {
expect(keys).to.have.length(6);
expect(keys2).to.have.length(7);
......@@ -444,10 +444,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
} else {
console.log("This test doesn't support " + dialect);
}
done();
});
});
});
});
......@@ -247,61 +247,46 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
});
});
it('executes select queries correctly', function(done) {
it('executes select queries correctly', function() {
var self = this;
self.sequelize.query(this.insertQuery).success(function() {
self.sequelize
.query('select * from ' + qq(self.User.tableName) + '')
.complete(function(err, users) {
expect(err).to.be.null;
expect(users.map(function(u) { return u.username; })).to.include('john');
done();
});
return self.sequelize.query(this.insertQuery).then(function() {
return self.sequelize.query('select * from ' + qq(self.User.tableName) + '');
}).spread(function(users) {
expect(users.map(function(u) { return u.username; })).to.include('john');
});
});
it('executes select queries correctly when quoteIdentifiers is false', function(done) {
it('executes select queries correctly when quoteIdentifiers is false', function() {
var self = this
, seq = Object.create(self.sequelize);
, seq = Object.create(self.sequelize);
seq.options.quoteIdentifiers = false;
seq.query(this.insertQuery).success(function() {
seq.query('select * from ' + qq(self.User.tableName) + '')
.complete(function(err, users) {
expect(err).to.be.null;
expect(users.map(function(u) { return u.username; })).to.include('john');
done();
});
return seq.query(this.insertQuery).then(function() {
return seq.query('select * from ' + qq(self.User.tableName) + '');
}).spread(function(users) {
expect(users.map(function(u) { return u.username; })).to.include('john');
});
});
it('executes select query with dot notation results', function(done) {
it('executes select query with dot notation results', function() {
var self = this;
self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).complete(function() {
self.sequelize.query(self.insertQuery).success(function() {
self.sequelize
.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + '')
.complete(function(err, users) {
expect(err).to.be.null;
expect(users).to.deep.equal([{'user.username': 'john'}]);
done();
});
});
return self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).then(function() {
return self.sequelize.query(self.insertQuery);
}).then(function() {
return self.sequelize.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + '');
}).spread(function( users) {
expect(users).to.deep.equal([{'user.username': 'john'}]);
});
});
it('executes select query with dot notation results and nest it', function(done) {
it('executes select query with dot notation results and nest it', function() {
var self = this;
self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).complete(function() {
self.sequelize.query(self.insertQuery).success(function() {
self.sequelize
.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + '', null, { raw: true, nest: true })
.complete(function(err, users) {
expect(err).to.be.null;
expect(users.map(function(u) { return u.user; })).to.deep.equal([{'username': 'john'}]);
done();
});
});
return self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).then(function() {
return self.sequelize.query(self.insertQuery);
}).then(function() {
return self.sequelize.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + '', null, { raw: true, nest: true });
}).then(function(users) {
expect(users.map(function(u) { return u.user; })).to.deep.equal([{'username': 'john'}]);
});
});
......@@ -325,24 +310,19 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
console.log('FIXME: I want to be supported in this dialect as well :-(');
}
it('uses the passed DAOFactory', function(done) {
var self = this;
self.sequelize.query(this.insertQuery).success(function() {
self.sequelize.query('SELECT * FROM ' + qq(self.User.tableName) + ';', self.User).success(function(users) {
expect(users[0].Model).to.equal(self.User);
done();
});
it('uses the passed model', function() {
return this.sequelize.query(this.insertQuery).bind(this).then(function() {
return this.sequelize.query('SELECT * FROM ' + qq(this.User.tableName) + ';', this.User, { type: this.sequelize.QueryTypes.SELECT });
}).then(function(users) {
expect(users[0].Model).to.equal(this.User);
});
});
it('dot separated attributes when doing a raw query without nest', function(done) {
it('dot separated attributes when doing a raw query without nest', function() {
var tickChar = (dialect === 'postgres' || dialect === 'mssql') ? '"' : '`'
, sql = 'select 1 as ' + Sequelize.Utils.addTicks('foo.bar.baz', tickChar);
this.sequelize.query(sql, null, { raw: true, nest: false }).success(function(result) {
expect(result).to.deep.equal([{ 'foo.bar.baz': 1 }]);
done();
});
return expect(this.sequelize.query(sql, null, { raw: true, nest: false }).get(0)).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]);
});
it('destructs dot separated attributes when doing a raw query using nest', function(done) {
......@@ -356,108 +336,87 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
});
it('replaces token with the passed array', function(done) {
this.sequelize.query('select ? as foo, ? as bar', null, { raw: true }, [1, 2]).success(function(result) {
this.sequelize.query('select ? as foo, ? as bar', null, { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2 }]);
done();
});
});
it('replaces named parameters with the passed object', function(done) {
this.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2 }]);
done();
});
it('replaces named parameters with the passed object', function() {
return expect(this.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0))
.to.eventually.deep.equal([{ foo: 1, bar: 2 }]);
});
it('replaces named parameters with the passed object and ignore those which does not qualify', function(done) {
this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]);
done();
});
it('replaces named parameters with the passed object and ignore those which does not qualify', function() {
return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0))
.to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]);
});
it('replaces named parameters with the passed object using the same key twice', function(done) {
this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]);
done();
});
it('replaces named parameters with the passed object using the same key twice', function() {
return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0))
.to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]);
});
it('replaces named parameters with the passed object having a null property', function(done) {
this.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, { one: 1, two: null }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: null }]);
done();
});
it('replaces named parameters with the passed object having a null property', function() {
return expect(this.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: { one: 1, two: null }}).get(0))
.to.eventually.deep.equal([{ foo: 1, bar: null }]);
});
it('throw an exception when key is missing in the passed object', function(done) {
it('throw an exception when key is missing in the passed object', function() {
var self = this;
expect(function() {
self.sequelize.query('select :one as foo, :two as bar, :three as baz', null, { raw: true }, { one: 1, two: 2 });
self.sequelize.query('select :one as foo, :two as bar, :three as baz', null, { raw: true, replacements: { one: 1, two: 2 }});
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g);
done();
});
it('throw an exception with the passed number', function(done) {
it('throw an exception with the passed number', function() {
var self = this;
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, 2);
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: 2 });
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g);
done();
});
it('throw an exception with the passed empty object', function(done) {
it('throw an exception with the passed empty object', function() {
var self = this;
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, {});
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: {}});
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g);
done();
});
it('throw an exception with the passed string', function(done) {
it('throw an exception with the passed string', function() {
var self = this;
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, 'foobar');
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: 'foobar'});
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g);
done();
});
it('throw an exception with the passed date', function(done) {
it('throw an exception with the passed date', function() {
var self = this;
expect(function() {
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, new Date());
self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: new Date()});
}).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g);
done();
});
it('handles AS in conjunction with functions just fine', function(done) {
it('handles AS in conjunction with functions just fine', function() {
var datetime = (dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()');
if (dialect === 'mssql') {
datetime = 'GETDATE()';
}
this.sequelize.query('SELECT ' + datetime + ' AS t').success(function(result) {
return this.sequelize.query('SELECT ' + datetime + ' AS t').spread(function(result) {
expect(moment(result[0].t).isValid()).to.be.true;
done();
});
});
if (Support.getTestDialect() === 'postgres') {
it('replaces named parameters with the passed object and ignores casts', function(done) {
this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]);
done();
});
it('replaces named parameters with the passed object and ignores casts', function() {
return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', null, { raw: true }, { one: 1, two: 2 }).get(0))
.to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]);
});
it('supports WITH queries', function(done) {
this
.sequelize
.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t')
.success(function(results) {
expect(results).to.deep.equal([{ 'sum': '5050' }]);
done();
});
it('supports WITH queries', function() {
return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').get(0))
.to.eventually.deep.equal([{ 'sum': '5050' }]);
});
}
});
......@@ -475,7 +434,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
this.t = t;
return this.sequelize.set({ foo: 'bar' }, { transaction: t });
}).then(function() {
return this.sequelize.query('SELECT @foo as `foo`', null, { raw: true, plain: true, transaction: this.t });
return this.sequelize.query('SELECT @foo as `foo`', { plain: true, transaction: this.t });
}).then(function(data) {
expect(data).to.be.ok;
expect(data.foo).to.be.equal('bar');
......@@ -491,7 +450,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
foos: 'bars'
}, { transaction: t });
}).then(function() {
return this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', null, { raw: true, plain: true, transaction: this.t });
return this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', { plain: true, transaction: this.t });
}).then(function(data) {
expect(data).to.be.ok;
expect(data.foo).to.be.equal('bar');
......@@ -915,87 +874,71 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
});
if (dialect === 'sqlite') {
it('correctly scopes transaction from other connections', function(done) {
it('correctly scopes transaction from other connections', function() {
var TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false })
, self = this;
var count = function(transaction, callback) {
var count = function(transaction) {
var sql = self.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] });
self
.sequelizeWithTransaction
.query(sql, null, { plain: true, raw: true, transaction: transaction })
.success(function(result) { callback(result.cnt); });
return self.sequelizeWithTransaction.query(sql, { plain: true, transaction: transaction }).then(function(result) {
return result.cnt;
});
};
TransactionTest.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction().then(function(t1) {
self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', null, { plain: true, raw: true, transaction: t1 }).success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(0);
count(t1, function(cnt) {
expect(cnt).to.equal(1);
t1.commit().success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(1);
done();
});
});
});
});
});
});
return TransactionTest.sync({ force: true }).bind(this).then(function() {
return self.sequelizeWithTransaction.transaction();
}).then(function(t1) {
this.t1 = t1;
return self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', { transaction: t1 });
}).then(function() {
return expect(count()).to.eventually.equal(0);
}).then(function(cnt) {
return expect(count(this.t1)).to.eventually.equal(1);
}).then(function () {
return this.t1.commit();
}).then(function() {
return expect(count()).to.eventually.equal(1);
});
});
} else {
it('correctly handles multiple transactions', function(done) {
it('correctly handles multiple transactions', function() {
var TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false })
, self = this;
var count = function(transaction, callback) {
var count = function(transaction) {
var sql = self.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] });
self
.sequelizeWithTransaction
.query(sql, null, { plain: true, raw: true, transaction: transaction })
.success(function(result) { callback(parseInt(result.cnt, 10)); });
return self.sequelizeWithTransaction.query(sql, { plain: true, transaction: transaction }).then(function(result) {
return parseInt(result.cnt, 10);
});
};
TransactionTest.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction().then(function(t1) {
self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', null, { plain: true, raw: true, transaction: t1 }).success(function() {
self.sequelizeWithTransaction.transaction().then(function(t2) {
self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'bar\');', null, { plain: true, raw: true, transaction: t2 }).success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(0);
count(t1, function(cnt) {
expect(cnt).to.equal(1);
count(t2, function(cnt) {
expect(cnt).to.equal(1);
t2.rollback().success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(0);
t1.commit().success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(1);
done();
});
});
});
});
});
});
});
});
});
});
});
return TransactionTest.sync({ force: true }).bind(this).then(function() {
return self.sequelizeWithTransaction.transaction();
}).then(function(t1) {
this.t1 = t1;
return self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', null, { transaction: t1 });
}).then(function() {
return self.sequelizeWithTransaction.transaction();
}).then(function(t2) {
this.t2 = t2;
return self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'bar\');', null, { transaction: t2 });
}).then(function() {
return expect(count()).to.eventually.equal(0);
}).then(function() {
return expect(count(this.t1)).to.eventually.equal(1);
}).then(function() {
return expect(count(this.t2)).to.eventually.equal(1);
}).then(function() {
return this.t2.rollback();
}).then(function() {
return expect(count()).to.eventually.equal(0);
}).then(function(cnt) {
return this.t1.commit();
}).then(function() {
return expect(count()).to.eventually.equal(1);
});
});
}
......
......@@ -20,7 +20,7 @@ describe(Support.getTestDialectTeaser('Sequelize#transaction'), function() {
.success(function() { done(); });
});
it('gets triggered once a transaction has been successfully rollbacked', function(done) {
it('gets triggered once a transaction has been successfully rolled back', function(done) {
this
.sequelize
.transaction().then(function(t) { t.rollback(); })
......@@ -28,59 +28,44 @@ describe(Support.getTestDialectTeaser('Sequelize#transaction'), function() {
});
if (Support.getTestDialect() !== 'sqlite') {
it('works for long running transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', {
it('works for long running transactions', function() {
this.timeout(10000);
return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
this.sequelize = sequelize;
this.User = sequelize.define('User', {
name: Support.Sequelize.STRING
}, { timestamps: false });
return sequelize.sync({ force: true }).success(function() {
return sequelize.transaction();
}).then(function(t) {
expect(t).to.be.ok;
var query = 'select sleep(2);';
switch (Support.getTestDialect()) {
case 'postgres':
query = 'select pg_sleep(2);';
break;
case 'sqlite':
query = 'select sqlite3_sleep(2000);';
break;
default:
break;
}
return sequelize.query(query, null, {
raw: true,
plain: true,
transaction: t
}).then(function() {
var dao = User.build({ name: 'foo' });
// this.QueryGenerator.insertQuery(tableName, values, dao.daoFactory.rawAttributes)
return query = sequelize
.getQueryInterface()
.QueryGenerator
.insertQuery(User.tableName, dao.values, User.rawAttributes);
}).then(function() {
return Promise.delay(1000);
}).then(function() {
return sequelize.query(query, null, {
raw: true,
plain: true,
transaction: t
});
}).then(function() {
return t.commit();
});
return sequelize.sync({ force: true });
}).then(function() {
return this.sequelize.transaction();
}).then(function(t) {
var query = 'select sleep(2);';
switch (Support.getTestDialect()) {
case 'postgres':
query = 'select pg_sleep(2);';
break;
case 'sqlite':
query = 'select sqlite3_sleep(2000);';
break;
default:
break;
}
return this.sequelize.query(query, { transaction: t }).bind(this).then(function() {
return this.User.create({ name: 'foo' });
}).then(function() {
return User.all().success(function(users) {
expect(users.length).to.equal(1);
expect(users[0].name).to.equal('foo');
done();
});
}).catch (done);
return this.sequelize.query(query, { transaction: t });
}).then(function() {
return t.commit();
});
}).then(function() {
return this.User.all();
}).then(function(users) {
expect(users.length).to.equal(1);
expect(users[0].name).to.equal('foo');
});
});
}
......
......@@ -28,8 +28,8 @@ if (dialect !== 'sqlite') {
var query = 'SELECT ' + now + ' as now';
return Promise.all([
this.sequelize.query(query),
this.sequelizeWithTimezone.query(query)
this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }),
this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT })
]).spread(function(now1, now2) {
var elapsedQueryTime = (Date.now() - startQueryTime) + 20;
expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime);
......
......@@ -98,7 +98,7 @@ describe(Support.getTestDialectTeaser('Transaction'), function() {
.then(function(t) {
return sequelize.sync({transaction:t})
.then(function( ) {
return t;
return t;
});
})
.then(function(t) {
......@@ -116,7 +116,7 @@ describe(Support.getTestDialectTeaser('Transaction'), function() {
.then(function(users) {
return expect(users.length).to.equal(1);
});
});
});
}
if (current.dialect.supports.lock) {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!