-[FEATURE] CLS Support. CLS is also used to automatically pass the transaction to any calls within the callback chain when using `sequelize.transaction(function() ...`.
-[BUG] Fixed issue with paranoid deletes and `deletedAt` with a custom field.
-[BUG] Fixed issue with paranoid deletes and `deletedAt` with a custom field.
Sequelize supports two ways of using transactions:
Sequelize supports two ways of using transactions, one will automatically commit or rollback the transaction based on a promise chain and the other leaves it up to the user.
* One which will automatically commit or rollback the transaction based on the result of a promise chain and, (if enabled) pass the transaction to all calls within the callback
* And one which leaves committing, rolling back and passing the transaction to the user.
The key difference is that the managed transaction uses a callback that expects a promise to be returned to it while the unmanaged transaction returns a promise.
The key difference is that the managed transaction uses a callback that expects a promise to be returned to it while the unmanaged transaction returns a promise.
// result is whatever the result of the promise chain returned to the transaction callback is
// result is whatever the result of the promise chain returned to the transaction callback
}).catch(function(err){
}).catch(function(err){
// Transaction has been rolled back
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback is
// err is whatever rejected the promise chain returned to the transaction callback
});
```
In the example above, the transaction is still manually passed, by passing `{ transaction: t }` as the second argument. To automatically pass the transaction to all queries you must install the [continuation local storage](https://github.com/othiym23/node-continuation-local-storage)(CLS) module and instantiate a namespace in your own code:
To enable CLS you must tell sequelize which namespace to use by setting it as a property on the sequelize constructor:
```js
varSequelize=require('sequelize');
Sequelize.cls=namespace;
newSequelize(....);
```
Notice, that the `cls` property must be set on the *constructor*, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances.
CLS works like a thread-local storage for callbacks. What this means in practice is, that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the `transaction` property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time:
```js
sequelize.transaction(function(t1){
namespace.get('transaction')===t1;
});
sequelize.transaction(function(t2){
namespace.get('transaction')===t2;
});
```
In most case you won't need to access `namespace.get('transaction')` directly, since all queries will automatically look for a transaction on the namespace:
```js
sequelize.transaction(function(t1){
// With CLS enabled, the user will be created inside the transaction
User.create({name:'Alice'});
});
```
If you want to execute queries inside the callback without using the transaction you can pass `{ transaction: null }`, or another transaction if you have several concurrent ones:
### Using transactions with other sequelize methods
# Using transactions with other sequelize methods
The `transaction` option goes with most other options, which are usually the first argument of a method.
The `transaction` option goes with most other options, which are usually the first argument of a method.
For methods that take values, like `.create`, `.update()`, `.updateAttributes()`and more`transaction` should be passed to the option in the second argument.
For methods that take values, like `.create`, `.update()`, `.updateAttributes()`etc.`transaction` should be passed to the option in the second argument.
If unsure, refer to the api documentation for the method you are using to be sure of the signature.
If unsure, refer to the API documentation for the method you are using to be sure of the signature.
* @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given
* @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given
* @param {Object} [options]
* @param {Object} [options]
* @param {Integer} [options.by=1] The number to increment by
* @param {Integer} [options.by=1] The number to increment by
* @param {String|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given
* @param {String|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given
* @param {Object} [options]
* @param {Object} [options]
* @param {Integer} [options.by=1] The number to decrement by
* @param {Integer} [options.by=1] The number to decrement by
* @param {Instance} [callee] If callee is provided, the returned data will be put into the callee
* @param {Instance} [callee] If callee is provided, the returned data will be put into the callee
* @param {Object} [options={}] Query options.
* @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 {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 {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 {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 {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 {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.
* If you have [CLS](https://github.com/othiym23/node-continuation-local-storage) enabled, the transaction will automatically be passed to any query that runs witin the callback.
* To enable CLS, add it do your project, create a namespace and set it on the sequelize constructor:
*
* ```js
* var cls = require('continuation-local-storage'),
* ns = cls.createNamespace('....');
* var Sequelize = require('sequelize');
* Sequelize.cls = ns;
* ```
* Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace
deprecated('Note: When passing a callback to a transaction a promise chain is expected in return, the transaction will be committed or rejected based on the promise chain returned to the callback.');
deprecated('Note: When passing a callback to a transaction a promise chain is expected in return, the transaction will be committed or rejected based on the promise chain returned to the callback.');
// We manually pass a context here, because the right context has to be available even though the .then callback is not strictly within the same callback chain
it('does not leak variables to the outer scope',function(){
it('does not leak variables to the outer scope',function(){
// This is a little tricky. We want to check the values in the outer scope, when the transaction has been successfully set up, but before it has been comitted.
// This is a little tricky. We want to check the values in the outer scope, when the transaction has been successfully set up, but before it has been comitted.
// We can't just call another function from inside that transaction, since that would transfer the context to that function - exactly what we are trying to prevent;
// We can't just call another function from inside that transaction, since that would transfer the context to that function - exactly what we are trying to prevent;
varself=this
,transactionSetup=false
,transactionEnded=false;
cb(this.sequelize,function(){
transactionSetup=true;
returnPromise.delay(500).then(function(){
expect(self.ns.get('transaction')).to.be.ok;
transactionEnded=true;
});
});
returnnewPromise(function(resolve){
// Wait for the transaction to be setup
varinterval=setInterval(function(){
if(transactionSetup){
clearInterval(interval);
resolve();
}
},200);
}).bind(this).then(function(){
expect(transactionEnded).not.to.be.ok;
expect(this.ns.get('transaction')).not.to.be.ok;
// Just to make sure it didn't change between our last check and the assertion
expect(transactionEnded).not.to.be.ok;
});
});
it('does not leak variables to the following promise chain',function(){