Commit bbbe2a3e by Carsten Brandt

Added support for transaction isolation levels

fixes #3220
parent 9637fa1d
...@@ -68,6 +68,7 @@ Yii Framework 2 Change Log ...@@ -68,6 +68,7 @@ Yii Framework 2 Change Log
- Enh #3132: `yii\rbac\PhpManager` now supports more compact data file format (qiangxue) - Enh #3132: `yii\rbac\PhpManager` now supports more compact data file format (qiangxue)
- Enh #3154: Added validation error display for `GridView` filters (ivan-kolmychek) - Enh #3154: Added validation error display for `GridView` filters (ivan-kolmychek)
- Enh #3196: Masked input upgraded to use jquery.inputmask plugin with more features. (kartik-v) - Enh #3196: Masked input upgraded to use jquery.inputmask plugin with more features. (kartik-v)
- Enh #3220: Added support for setting transation isolation levels (cebe)
- Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2) - Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2)
- Enh #3230: Added `yii\filters\AccessControl::user` to support access control with different actors (qiangxue) - Enh #3230: Added `yii\filters\AccessControl::user` to support access control with different actors (qiangxue)
- Enh #3232: Added `export()` and `exportAsString()` methods to `yii\helpers\BaseVarDumper` (klimov-paul) - Enh #3232: Added `export()` and `exportAsString()` methods to `yii\helpers\BaseVarDumper` (klimov-paul)
......
...@@ -410,16 +410,18 @@ class Connection extends Component ...@@ -410,16 +410,18 @@ class Connection extends Component
/** /**
* Starts a transaction. * Starts a transaction.
* @param string|null $isolationLevel The isolation level to use for this transaction.
* See [[Transaction::begin()]] for details.
* @return Transaction the transaction initiated * @return Transaction the transaction initiated
*/ */
public function beginTransaction() public function beginTransaction($isolationLevel = null)
{ {
$this->open(); $this->open();
if (($transaction = $this->getTransaction()) === null) { if (($transaction = $this->getTransaction()) === null) {
$transaction = $this->_transaction = new Transaction(['db' => $this]); $transaction = $this->_transaction = new Transaction(['db' => $this]);
} }
$transaction->begin(); $transaction->begin($isolationLevel);
return $transaction; return $transaction;
} }
......
...@@ -340,6 +340,19 @@ abstract class Schema extends Object ...@@ -340,6 +340,19 @@ abstract class Schema extends Object
} }
/** /**
* Sets the isolation level of the current transaction.
* @param string $level The transaction isolation level to use for this transaction.
* This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
* and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
* after `SET TRANSACTION ISOLATION LEVEL`.
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
*/
public function setTransactionIsolationLevel($level)
{
$this->db->createCommand("SET TRANSACTION ISOLATION LEVEL $level;")->execute();
}
/**
* Quotes a string value for use in a query. * Quotes a string value for use in a query.
* Note that if the parameter is not a string, it will be returned without change. * Note that if the parameter is not a string, it will be returned without change.
* @param string $str string to be quoted * @param string $str string to be quoted
......
...@@ -39,6 +39,27 @@ use yii\base\InvalidConfigException; ...@@ -39,6 +39,27 @@ use yii\base\InvalidConfigException;
class Transaction extends \yii\base\Object class Transaction extends \yii\base\Object
{ {
/** /**
* A constant representing the transaction isolation level `READ UNCOMMITTED`.
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
*/
const READ_UNCOMMITTED = 'READ UNCOMMITTED';
/**
* A constant representing the transaction isolation level `READ COMMITTED`.
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
*/
const READ_COMMITTED = 'READ COMMITTED';
/**
* A constant representing the transaction isolation level `REPEATABLE READ`.
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
*/
const REPEATABLE_READ = 'REPEATABLE READ';
/**
* A constant representing the transaction isolation level `SERIALIZABLE`.
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
*/
const SERIALIZABLE = 'SERIALIZABLE';
/**
* @var Connection the database connection that this transaction is associated with. * @var Connection the database connection that this transaction is associated with.
*/ */
public $db; public $db;
...@@ -47,6 +68,7 @@ class Transaction extends \yii\base\Object ...@@ -47,6 +68,7 @@ class Transaction extends \yii\base\Object
*/ */
private $_level = 0; private $_level = 0;
/** /**
* Returns a value indicating whether this transaction is active. * Returns a value indicating whether this transaction is active.
* @return boolean whether this transaction is active. Only an active transaction * @return boolean whether this transaction is active. Only an active transaction
...@@ -59,9 +81,15 @@ class Transaction extends \yii\base\Object ...@@ -59,9 +81,15 @@ class Transaction extends \yii\base\Object
/** /**
* Begins a transaction. * Begins a transaction.
* @param string|null $isolationLevel The [isolation level][] to use for this transaction.
* This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but
* also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
* If not specified (`null`) the isolation level will not be set explicitly and the DBMS default will be used.
*
* [isolation level]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
* @throws InvalidConfigException if [[db]] is `null`. * @throws InvalidConfigException if [[db]] is `null`.
*/ */
public function begin() public function begin($isolationLevel = null)
{ {
if ($this->db === null) { if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.'); throw new InvalidConfigException('Transaction::db must be set.');
...@@ -69,7 +97,10 @@ class Transaction extends \yii\base\Object ...@@ -69,7 +97,10 @@ class Transaction extends \yii\base\Object
$this->db->open(); $this->db->open();
if ($this->_level == 0) { if ($this->_level == 0) {
Yii::trace('Begin transaction', __METHOD__); if ($isolationLevel !== null) {
$this->db->getSchema()->setTransactionIsolationLevel($isolationLevel);
}
Yii::trace('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__);
$this->db->pdo->beginTransaction(); $this->db->pdo->beginTransaction();
$this->_level = 1; $this->_level = 1;
...@@ -141,4 +172,25 @@ class Transaction extends \yii\base\Object ...@@ -141,4 +172,25 @@ class Transaction extends \yii\base\Object
throw new Exception('Roll back failed: nested transaction not supported.'); throw new Exception('Roll back failed: nested transaction not supported.');
} }
} }
/**
* Sets the transaction isolation level for this transaction.
*
* This method can be used to set the isolation level while the transaction is already active.
* However this is not supported by all DBMS so you might rather specify the isolation level directly
* when calling [[begin()]].
* @param string $level The transaction isolation level to use for this transaction.
* This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but
* also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
* @throws Exception if the transaction is not active
* @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
*/
public function setIsolationLevel($level)
{
if (!$this->getIsActive()) {
throw new Exception('Failed to set isolation level: transaction was inactive.');
}
Yii::trace('Setting transaction isolation level to ' . $level, __METHOD__);
$this->db->getSchema()->setTransactionIsolationLevel($level);
}
} }
...@@ -7,8 +7,10 @@ ...@@ -7,8 +7,10 @@
namespace yii\db\sqlite; namespace yii\db\sqlite;
use yii\base\NotSupportedException;
use yii\db\TableSchema; use yii\db\TableSchema;
use yii\db\ColumnSchema; use yii\db\ColumnSchema;
use yii\db\Transaction;
/** /**
* Schema is the class for retrieving metadata from a SQLite (2/3) database. * Schema is the class for retrieving metadata from a SQLite (2/3) database.
...@@ -249,4 +251,27 @@ class Schema extends \yii\db\Schema ...@@ -249,4 +251,27 @@ class Schema extends \yii\db\Schema
return $column; return $column;
} }
/**
* Sets the isolation level of the current transaction.
* @param string $level The transaction isolation level to use for this transaction.
* This can be either [[Transaction::READ_UNCOMMITTED]] or [[Transaction::SERIALIZABLE]].
* @throws \yii\base\NotSupportedException when unsupported isolation levels are used.
* SQLite only supports SERIALIZABLE and READ UNCOMMITTED.
* @see http://www.sqlite.org/pragma.html#pragma_read_uncommitted
*/
public function setTransactionIsolationLevel($level)
{
switch($level)
{
case Transaction::SERIALIZABLE:
$this->db->createCommand("PRAGMA read_uncommitted = False;")->execute();
break;
case Transaction::READ_UNCOMMITTED:
$this->db->createCommand("PRAGMA read_uncommitted = True;")->execute();
break;
default:
throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.');
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment