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

Commit 38760fbc by Mick Hansen

Merge branch 'master' of github.com:sequelize/sequelize

2 parents cc80c087 3b39ac6b
......@@ -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 compatibility 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() ...`.
......@@ -23,7 +28,7 @@
- [BUG] `instance.save()` with `fields: []` (as a result of `.changed()` being `[]`) will no result in a noop instead of an empty update query.
- [BUG] Fixed case where `findOrCreate` could return `[null, true]` when given a `defaults` value that triggered a unique constraint error.
#### Backwards compatability changes
#### Backwards compatibility changes
- `instance.update()` using default fields will now automatically also save and validate values provided via `beforeUpdate` hooks
- Sequelize no longer supports case insensitive mysql enums
- `pg-hstore` has been moved to a devDependency, Postgres users will have to install `pg-hstore` manually alongside `pg`: `$ npm install pg pg-hstore`
......@@ -39,7 +44,7 @@
- [BUG] Sequelize will no longer fail on a postgres constraint error not defined by Sequelize
- [FEATURE] It's now possible to pass an association reference to include. `var Owner = Company.belongsTo(User, {as: 'owner'}; Company.findOne({include: [Owner]});`
#### Backwards compatability changes
#### Backwards compatibility changes
- When updating an instance `_previousDataValues` will now be updated after `afterUpdate` hooks have been run rather than before allowing you to use `changed` in `afterUpdate`
# 2.0.0-rc4
......@@ -52,7 +57,7 @@
- [BUG] Fixed crash/bug when using `include.where` together with `association.scope`
- [BUG] Fixed support for `Instance.destroy()` and `field` for postgres.
#### Backwards compatability changes
#### Backwards compatibility changes
- Some of the string error messages for connection errors have been replaced with actual error instances. Checking for connection errors should now be more consistent.
# 2.0.0-rc3
......@@ -72,7 +77,7 @@
- [INTERNALS] Update `inflection` dependency to v1.5.2
- [REMOVED] Remove query generation syntactic sugar provided by `node-sql`, as well as the dependency on that module
#### Backwards compatability changes
#### Backwards compatibility changes
- When eager-loading a many-to-many association, the attributes of the through table are now accessible through an attribute named after the through model rather than the through table name singularized. i.e. `Task.find({include: Worker})` where the table name for through model `TaskWorker` is `TableTaskWorkers` used to produce `{ Worker: { ..., TableTaskWorker: {...} } }`. It now produces `{ Worker: { ..., TaskWorker: {...} } }`. Does not affect models where table name is auto-defined by Sequelize, or where table name is model name pluralized.
- When using `Model#find()` with an `order` clause, the table name is prepended to the `ORDER BY` SQL. e.g. `ORDER BY Task.id` rather than `ORDER BY id`. The change is to avoid ambiguous column names where there are eager-loaded associations with the same column names. A side effect is that code like `Task.findAll( { include: [ User ], order: [ [ 'Users.id', 'ASC' ] ] } )` will now throw an error. This should be achieved with `Task.findAll( { include: [ User ], order: [ [ User, 'id', 'ASC' ] ] } )` instead.
- Nested HSTORE objects are no longer supported. Use DataTypes.JSON instead.
......@@ -99,7 +104,7 @@
- [FEATURE] Hooks need not return a result - undefined return is interpreted as a resolved promise
- [FEATURE] Added `find()` hooks
#### Backwards compatability changes
#### Backwards compatibility changes
- The `fieldName` property, used in associations with a foreign key object `(A.hasMany(B, { foreignKey: { ... }})`, has been renamed to `name` to avoid confusion with `field`.
- The naming of the join table entry for N:M association getters is now singular (like includes)
- Signature of hooks has changed to pass options to all hooks. Any hooks previously defined like `Model.beforeCreate(values)` now need to be `Model.beforeCreate(values, options)` etc.
......@@ -131,7 +136,7 @@ We are working our way to the first 2.0.0 release candidate.
+ 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 compatibility 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:
+ Person is now pluralized as people instead of persons
- Accesors for models with underscored names are no longer camel cased automatically. For example, if you have a model with name `my_model`, and `my_other_model.hasMany(my_model)`, the getter will now be `instance_of_my_model.getMy_model` instead of `.getMyModel`.
......@@ -170,7 +175,7 @@ We are working our way to the first 2.0.0 release candidate.
- [BUG] Create a composite primary key for doubled linked self reference [#1891](https://github.com/sequelize/sequelize/issues/1891)
- [INTERNALS] `bulkDeleteQuery` was removed from the MySQL / abstract query generator, since it was never used internally. Please use `deleteQuery` instead.
#### Backwards compatability changes
#### Backwards compatibility changes
- Sequelize now returns promises instead of its custom event emitter from most calls. This affects methods that return multiple values (like `findOrCreate` or `findOrInitialize`). If your current callbacks do not accept the 2nd success parameter you might be seeing an array as the first param. Either use `.spread()` for these methods or add another argument to your callback: `.success(instance)` -> `.success(instance, created)`.
- `.success()`/`.done()` and any other non promise methods are now deprecated (we will keep the codebase around for a few versions though). on('sql') persists for debugging purposes.
- Model association calls (belongsTo/hasOne/hasMany) are no longer chainable. (this is to support being able to pass association references to include rather than model/as combinations)
......@@ -195,7 +200,7 @@ We are working our way to the first 2.0.0 release candidate.
- [FEATURE/BUG] hstore values are now parsed on find/findAll. Thanks to @nunofgs [#1560](https://github.com/sequelize/sequelize/pull/1560)
- [FEATURE] Read cli options from a file. Thanks to @codeinvain [#1540](https://github.com/sequelize/sequelize/pull/1540)
#### Backwards compatability changes
#### Backwards compatibility changes
- The `notNull` validator has been removed, use the Schema's `allowNull` property.
- All Validation errors now return a sequelize.ValidationError which inherits from Error.
- selectedValues has been removed for performance reasons, if you depend on this, please open an issue and we will help you work around it.
......
......@@ -72,12 +72,13 @@ The Sequelize constructor takes a `define` option which will be used as the defa
```js
var sequelize = new Sequelize('connectionUri', {
timestamps: false // true by default
define: {
timestamps: false // true by default
}
});
var User = sequelize.define('user', {}); // timestamps is false by default
var Post = sequelize.define('user', {}, {
timestamps: true // timestamps will now be true
});
```
......@@ -474,7 +474,7 @@ Task.drop() // will emit success or failure event
Project.[sync|drop]().then(function() {
// ok ... everything is nice!
}).catch(function(error) {
// oooh, did you entered wrong database credentials?
// oooh, did you enter wrong database credentials?
})
```
......@@ -584,7 +584,7 @@ Project.find({
### findOrCreate - Search for a specific element or create it if not available
The method `findOrCreate` can be used to check if a certain element is already existing in the database. If that is the case the method will result in a respective instance. If the element does not yet exist, it will be created.
The method `findOrCreate` can be used to check if a certain element already exists in the database. If that is the case the method will result in a respective instance. If the element does not yet exist, it will be created.
Let's assume we have an empty database with a `User` model which has a `username` and a `job`.
......@@ -637,7 +637,7 @@ User
### findAndCountAll - Search for multiple elements in the database, returns both data and total count
This is a convienience method that combines`findAll()`and`count()`(see below), this is useful when dealing with queries related to pagination where you want to retrieve data with a`limit`and`offset`but also need to know the total number of records that match the query.
This is a convienience method that combines`findAll`()and `count`()(see below), this is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query.
The success handler will always receive an object with two properties:
......@@ -660,7 +660,7 @@ Project
});
```
The options [object] that you pass to`findAndCountAll()`is the same as for`findAll()`(described below).
The options [object] that you pass to`findAndCountAll`()is the same as for`findAll`()(described below).
### findAll - Search for multiple elements in the database
```js
......@@ -900,7 +900,7 @@ Project.sum('age', { where: { age: { gt: 5 } } }).then(function(sum) {
## Eager loading
When you are retrieving data from the database there is a fair chance that you also want to get their associations. This is possible since`v1.6.0`and is called eager loading. The basic idea behind that, is the use of the attribute`include`when you are calling`find`or`findAll`. Lets assume the following setup:
When you are retrieving data from the database there is a fair chance that you also want to get their associations. This is possible since`v1.6.0`and is called eager loading. The basic idea behind that, is the use of the attribute `include` when you are calling `find` or `findAll`. Lets assume the following setup:
```js
var User = sequelize.define('User', { name: Sequelize.STRING })
......@@ -968,7 +968,7 @@ User.findAll({ include: [ Task ] }).then(function(users) {
Notice that the accessor is plural. This is because the association is many-to-something.
If an association is aliased (using the`as`option), you_must_specify this alias when including the model. Notice how the user's`Tool`s are aliased as`Instruments`above. In order to get that right you have to specify the model you want to load, as well as the alias:
If an association is aliased (using the`as`option), you must specify this alias when including the model. Notice how the user's `Tool`s are aliased as `Instruments` above. In order to get that right you have to specify the model you want to load, as well as the alias:
```js
User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(function(users) {
......
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
......@@ -243,7 +243,7 @@ module.exports = (function() {
value = this.escape(value, (modelAttributeMap && modelAttributeMap[key]) || undefined);
if (options.exception) {
// $ inside value strings are illegal when using $$ as literal strings/delimiters for function bodys
value = value.replace(/\$/g, '\\$');
value = value.toString().replace(/\$/g, '\\$');
}
values.push(value);
}
......
......@@ -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;
......
......@@ -95,7 +95,7 @@ ConnectionManager.prototype.connect = function(config) {
}).tap(function (connection) {
if (self.sequelize.config.keepDefaultTimezone) return;
return new Promise(function (resolve, reject) {
connection.query("SET TIME ZONE INTERVAL '" + self.sequelize.options.timezone + "' HOUR TO MINUTE").on('error', function (err) {
connection.query("SET client_min_messages TO warning; SET TIME ZONE INTERVAL '" + self.sequelize.options.timezone + "' HOUR TO MINUTE").on('error', function (err) {
reject(err);
}).on('end', function () {
resolve();
......
......@@ -869,8 +869,6 @@ module.exports = (function() {
return dataType;
},
quoteIdentifier: function(identifier, force) {
var _ = Utils._;
if (identifier === '*') return identifier;
......
......@@ -147,67 +147,65 @@ 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) {
if (!self.options.returning) {
return result.rowCount;
return parseInt(result.rowCount, 10);
}
if (!!self.callee && !!self.callee._hasHstoreAttributes) {
......@@ -218,7 +216,7 @@ module.exports = (function() {
return self.handleSelectQuery(rows);
} else if (QueryTypes.BULKDELETE === self.options.type) {
return result.rowCount;
return parseInt(result.rowCount, 10);
} else if (self.isUpsertQuery()) {
return rows[0].sequelize_upsert;
} else if (self.isInsertQuery() || self.isUpdateQuery()) {
......@@ -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;
}
......@@ -256,10 +256,14 @@ module.exports = (function() {
, table
, index;
switch (err.code) {
var code = err.code || err.sqlState
, errMessage = err.message || err.messagePrimary
, errDetail = err.detail || err.messageDetail;
switch (code) {
case '23503':
index = err.message.match(/violates foreign key constraint \"(.+?)\"/)[1];
table = err.message.match(/on table \"(.+?)\"/)[1];
index = errMessage.match(/violates foreign key constraint \"(.+?)\"/)[1];
table = errMessage.match(/on table \"(.+?)\"/)[1];
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
......@@ -270,7 +274,7 @@ module.exports = (function() {
case '23505':
// there are multiple different formats of error messages for this error code
// this regex should check at least two
match = err.detail.match(/Key \((.*?)\)=\((.*?)\)/);
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) {
var fields = Utils._.zipObject(match[1].split(', '), match[2].split(', '))
......@@ -299,7 +303,7 @@ module.exports = (function() {
});
} else {
return new sequelizeErrors.UniqueConstraintError({
message: err.message,
message: errMessage,
parent: err
});
}
......
......@@ -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);
......
......@@ -21,8 +21,8 @@ error.BaseError = function() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'SequelizeBaseError';
Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
this.message = tmp.message;
Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
};
util.inherits(error.BaseError, Error);
......
......@@ -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);
};
/**
......
......@@ -25,8 +25,9 @@ module.exports = (function() {
};
ModelManager.prototype.getDAO = function(daoName, options) {
options = options || {};
options.attribute = options.attribute || 'name';
options = _.defaults(options || {}, {
attribute: 'name'
});
var dao = this.daos.filter(function(dao) {
return dao[options.attribute] === daoName;
......
......@@ -718,14 +718,13 @@ module.exports = (function() {
}
mapFieldNames.call(this, options, this);
options = paranoidClause.call(this, options);
options = paranoidClause(this, options);
if (options.hooks) {
return this.runHooks('beforeFindAfterOptions', options);
}
}).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 }));
};
/**
......@@ -814,7 +811,7 @@ module.exports = (function() {
}
}
options = paranoidClause.call(this, options);
options = paranoidClause(this, options);
return this.QueryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this);
};
......@@ -1252,6 +1249,7 @@ module.exports = (function() {
* @param {Boolean} [options.individualHooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if options.hooks is true.
* @param {Boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by postgres)
* @param {Array} [options.updateOnDuplicate] Fields to update if row key already exists (on duplicate key update)? (only supported by mysql & mariadb). By default, all fields are updated.
* @param {Transaction} [options.transaction]
*
* @return {Promise<Array<Instance>>}
*/
......@@ -1448,7 +1446,6 @@ module.exports = (function() {
options.type = QueryTypes.BULKDELETE;
mapFieldNames.call(this, options, this);
return Promise.try(function() {
......@@ -1796,38 +1793,35 @@ module.exports = (function() {
// private
var paranoidClause = function(options) {
if (! this.options.timestamps || ! this.options.paranoid || options.paranoid === false) return options || {};
// validateIncludedElements should have been called before this method
var paranoidClause = function(model, options) {
options = options || {};
options.where = options.where || {};
// Apply on each include
// This should be handled before handling where conditions because of logic with returns
// otherwise this code will never run on includes of a already conditionable where
if (options.include && options.include.length) {
if (options.include) {
options.include.forEach(function(include) {
if (Utils._.isPlainObject(include) && include.model) {
paranoidClause.call(include.model, include);
}
paranoidClause(include.model, include);
});
}
var deletedAtCol = this._timestampAttributes.deletedAt
if (!model.options.timestamps || !model.options.paranoid || options.paranoid === false) {
// This model is not paranoid, nothing to do here;
return options;
}
var deletedAtCol = model._timestampAttributes.deletedAt
, deletedAtObject = {};
deletedAtObject[this.rawAttributes[deletedAtCol].field || deletedAtCol] = null;
deletedAtObject[model.rawAttributes[deletedAtCol].field || deletedAtCol] = null;
// detect emptiness
if(
Utils._.isObject(options.where) && Object.keys( options.where ).length === 0 ||
( Utils._.isString(options.where) || Utils._.isArray(options.where) ) && options.where.length === 0
) {
if (Utils._.isEmpty(options.where)) {
options.where = deletedAtObject;
return options;
} else {
options.where = model.sequelize.and(deletedAtObject, options.where);
}
options.where = this.sequelize.and( deletedAtObject, options.where );
return options;
};
......
......@@ -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
......@@ -187,7 +188,7 @@ module.exports = (function() {
// enum type within the table and attribute
if (self.sequelize.options.dialect === 'postgres') {
// Find the table that we're trying to drop
var daoTable = self.sequelize.daoFactoryManager.getDAO(tableName, 'tableName');
var daoTable = self.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' });
if (!!daoTable) {
var getTableName = (!options || !options.schema || options.schema === 'public' ? '' : options.schema + '_') + tableName;
......@@ -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');
}
......
......@@ -170,7 +170,7 @@ SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
};
SqlString.dateToString = function(date, timeZone, dialect) {
date = moment(date).zone(timeZone);
date = moment(date).utcOffset(timeZone);
if (dialect === 'mysql' || dialect === 'mariadb') {
return date.format('YYYY-MM-DD HH:mm:ss');
......
......@@ -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']
......@@ -37,7 +37,7 @@
"generic-pool": "2.1.1",
"inflection": "1.5.3",
"lodash": "~2.4.0",
"moment": "~2.8.0",
"moment": "^2.9.0",
"node-uuid": "~1.4.1",
"toposort-class": "~0.3.0",
"validator": "~3.22.1"
......@@ -47,7 +47,8 @@
"chai-as-promised": "^4.1.1",
"sqlite3": "~3.0.0",
"mysql": "~2.5.0",
"pg": "~3.6.0",
"pg": "^4.2.0",
"pg-native": "^1.8.0",
"pg-hstore": "^2.3.1",
"tedious": "^1.7.0",
"watchr": "~2.4.11",
......
......@@ -22,7 +22,7 @@ describe(Support.getTestDialectTeaser('Configuration'), function() {
if (dialect === 'sqlite') {
// SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors.
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file');
} else if (dialect === 'mssql') {
} else if (dialect === 'mssql' || dialect === 'postgres') {
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith([seq.HostNotReachableError, seq.InvalidConnectionError]);
} else {
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.InvalidConnectionError, 'connect EINVAL');
......
......@@ -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);
});
});
......
......@@ -101,4 +101,41 @@ describe(Support.getTestDialectTeaser('Paranoid'), function() {
});
});
it('should not load paranoid, destroyed instances, with a non-paranoid parent', function () {
var X = this.sequelize.define('x', {
name: DataTypes.STRING
}, {
paranoid: false
});
var Y = this.sequelize.define('y', {
name: DataTypes.STRING
}, {
timestamps: true,
paranoid: true
});
X.hasMany(Y);
return this.sequelize.sync({ force: true}).bind(this).then(function () {
return this.sequelize.Promise.all([
X.create(),
Y.create()
]);
}).spread(function (x, y) {
this.x = x;
this.y = y;
return x.addY(y);
}).then(function () {
return this.y.destroy();
}).then(function () {
return X.findAll({
include: [Y]
}).get(0);
}).then(function (x) {
expect(x.ys).to.have.length(0);
});
});
});
......@@ -99,7 +99,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
username: 'gottlieb'
}
}).then(function () {
throw new Error('I should have ben rejected');
throw new Error('I should have been rejected');
}, function (err) {
expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok;
expect(err.fields).to.be.ok;
......@@ -130,6 +130,27 @@ describe(Support.getTestDialectTeaser('Model'), function() {
});
});
it('should support bools in defaults', function () {
var User = this.sequelize.define('user', {
objectId: {
type: DataTypes.INTEGER,
unique: true
},
bool: DataTypes.BOOLEAN
});
return User.sync({force: true}).then(function () {
return User.findOrCreate({
where: {
objectId: 1
},
defaults: {
bool: false
}
});
});
});
it('returns instance if already existent. Single find field.', function(done) {
var self = this,
data = {
......
......@@ -1300,7 +1300,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
it('sorts the results via a date column', function(done) {
var self = this;
self.User.create({username: 'user3', data: 'bar', theDate: moment().add('hours', 2).toDate()}).success(function() {
self.User.create({username: 'user3', data: 'bar', theDate: moment().add(2, 'hours').toDate()}).success(function() {
self.User.findAll({ order: [['theDate', 'DESC']] }).success(function(users) {
expect(users[0].id).to.be.above(users[2].id);
done();
......
......@@ -18,8 +18,6 @@ chai.config.includeStack = true;
describe(Support.getTestDialectTeaser('Model'), function() {
beforeEach(function() {
this.clock = sinon.useFakeTimers();
this.User = this.sequelize.define('user', {
username: DataTypes.STRING,
foo: {
......@@ -35,10 +33,6 @@ describe(Support.getTestDialectTeaser('Model'), function() {
return this.sequelize.sync({ force: true });
});
afterEach(function() {
this.clock.restore();
});
if (current.dialect.supports.upserts) {
describe('upsert', function() {
it('works with upsert on id', function() {
......@@ -49,8 +43,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
expect(created).to.be.ok;
}
this.clock.tick(2000); // Make sure to pass some time so updatedAt != createdAt
return this.User.upsert({ id: 42, username: 'doe' });
return this.sequelize.Promise.delay(1000).bind(this).then(function() {
return this.User.upsert({ id: 42, username: 'doe' });
});
}).then(function(created) {
if (dialect === 'sqlite') {
expect(created).not.to.be.defined;
......@@ -74,8 +69,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
expect(created).to.be.ok;
}
this.clock.tick(2000); // Make sure to pass some time so updatedAt != createdAt
return this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' });
return this.sequelize.Promise.delay(1000).bind(this).then(function() {
return this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' });
});
}).then(function(created) {
if (dialect === 'sqlite') {
expect(created).not.to.be.defined;
......
......@@ -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();
});
});
});
});
......@@ -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!