Navigation Menu

Skip to content

Commit

Permalink
Merge branch '2.2-nested-transaction' into 2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
jrbasso committed Apr 23, 2012
2 parents ed0c5a4 + 3715780 commit 7fe333c
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 86 deletions.
29 changes: 9 additions & 20 deletions lib/Cake/Model/Datasource/Database/Mysql.php
Expand Up @@ -77,17 +77,6 @@ class Mysql extends DboSource {
*/
protected $_useAlias = true;

/**
* Index of basic SQL commands
*
* @var array
*/
protected $_commands = array(
'begin' => 'START TRANSACTION',
'commit' => 'COMMIT',
'rollback' => 'ROLLBACK'
);

/**
* List of engine specific additional field parameters used on table creating
*
Expand Down Expand Up @@ -262,15 +251,6 @@ public function getEncoding() {
return $this->_execute('SHOW VARIABLES LIKE ?', array('character_set_client'))->fetchObject()->Value;
}

/**
* Gets the version string of the database server
*
* @return string The database encoding
*/
public function getVersion() {
return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
}

/**
* Query charset by collation
*
Expand Down Expand Up @@ -696,4 +676,13 @@ public function getSchemaName() {
return $this->config['database'];
}

/**
* Check if the server support nested transactions
*
* @return boolean
*/
public function supportNestedTransaction() {
return $this->nestedTransaction && version_compare($this->getVersion(), '4.1', '>=');
}

}
20 changes: 9 additions & 11 deletions lib/Cake/Model/Datasource/Database/Postgres.php
Expand Up @@ -33,17 +33,6 @@ class Postgres extends DboSource {
*/
public $description = "PostgreSQL DBO Driver";

/**
* Index of basic SQL commands
*
* @var array
*/
protected $_commands = array(
'begin' => 'BEGIN',
'commit' => 'COMMIT',
'rollback' => 'ROLLBACK'
);

/**
* Base driver configuration settings. Merged with user settings.
*
Expand Down Expand Up @@ -906,4 +895,13 @@ public function getSchemaName() {
return $this->config['schema'];
}

/**
* Check if the server support nested transactions
*
* @return boolean
*/
public function supportNestedTransaction() {
return $this->nestedTransaction && version_compare($this->getVersion(), '8.0', '>=');
}

}
9 changes: 9 additions & 0 deletions lib/Cake/Model/Datasource/Database/Sqlite.php
Expand Up @@ -559,4 +559,13 @@ public function getSchemaName() {
return "main"; // Sqlite Datasource does not support multidb
}

/**
* Check if the server support nested transactions
*
* @return boolean
*/
public function supportNestedTransaction() {
return $this->nestedTransaction && version_compare($this->getVersion(), '3.6.8', '>=');
}

}
22 changes: 1 addition & 21 deletions lib/Cake/Model/Datasource/Database/Sqlserver.php
Expand Up @@ -98,31 +98,12 @@ class Sqlserver extends DboSource {
'boolean' => array('name' => 'bit')
);

/**
* Index of basic SQL commands
*
* @var array
*/
protected $_commands = array(
'begin' => 'BEGIN TRANSACTION',
'commit' => 'COMMIT',
'rollback' => 'ROLLBACK'
);

/**
* Magic column name used to provide pagination support for SQLServer 2008
* which lacks proper limit/offset support.
*/
const ROW_COUNTER = '_cake_page_rownum_';

/**
* The version of SQLServer being used. If greater than 11
* Normal limit offset statements will be used
*
* @var string
*/
protected $_version;

/**
* Connects to the database using options in the given configuration array.
*
Expand Down Expand Up @@ -151,7 +132,6 @@ public function connect() {
throw new MissingConnectionException(array('class' => $e->getMessage()));
}

$this->_version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
return $this->connected;
}

Expand Down Expand Up @@ -515,7 +495,7 @@ public function renderStatement($type, $data) {
}

// For older versions use the subquery version of pagination.
if (version_compare($this->_version, '11', '<') && preg_match('/FETCH\sFIRST\s+([0-9]+)/i', $limit, $offset)) {
if (version_compare($this->getVersion(), '11', '<') && preg_match('/FETCH\sFIRST\s+([0-9]+)/i', $limit, $offset)) {
preg_match('/OFFSET\s*(\d+)\s*.*?(\d+)\s*ROWS/', $limit, $limitOffset);

$limit = 'TOP ' . intval($limitOffset[2]);
Expand Down
142 changes: 109 additions & 33 deletions lib/Cake/Model/Datasource/DboSource.php
Expand Up @@ -69,6 +69,15 @@ class DboSource extends DataSource {
*/
public $cacheMethods = true;

/**
* Flag to support nested transactions. If it is set to false, you will be able to use
* the transaction methods (begin/commit/rollback), but just the global transaction will
* be executed.
*
* @var boolean
*/
public $nestedTransaction = true;

/**
* Print full query debug info?
*
Expand Down Expand Up @@ -183,17 +192,6 @@ class DboSource extends DataSource {
*/
protected $_transactionNesting = 0;

/**
* Index of basic SQL commands
*
* @var array
*/
protected $_commands = array(
'begin' => 'BEGIN',
'commit' => 'COMMIT',
'rollback' => 'ROLLBACK'
);

/**
* Default fields that are used by the DBO
*
Expand Down Expand Up @@ -294,12 +292,21 @@ public function disconnect() {
/**
* Get the underlying connection object.
*
* @return PDOConnection
* @return PDO
*/
public function getConnection() {
return $this->_connection;
}

/**
* Gets the version string of the database server
*
* @return string The database version
*/
public function getVersion() {
return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
}

/**
* Returns a quoted and escaped string of $data for use in an SQL statement.
*
Expand Down Expand Up @@ -2019,6 +2026,15 @@ public function truncate($table) {
return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table));
}

/**
* Check if the server support nested transactions
*
* @return boolean
*/
public function supportNestedTransaction() {
return false;
}

/**
* Begin a transaction
*
Expand All @@ -2027,15 +2043,33 @@ public function truncate($table) {
* or a transaction has not started).
*/
public function begin() {
if ($this->_transactionStarted || $this->_connection->beginTransaction()) {
if ($this->fullDebug && empty($this->_transactionNesting)) {
$this->logQuery('BEGIN');
if ($this->_transactionStarted) {
if ($this->supportNestedTransaction()) {
return $this->_beginNested();
}
$this->_transactionStarted = true;
$this->_transactionNesting++;
return true;
return $this->_transactionStarted;
}
return false;

$this->_transactionNesting = 0;
if ($this->fullDebug) {
$this->logQuery('BEGIN');
}
return $this->_transactionStarted = $this->_connection->beginTransaction();
}

/**
* Begin a nested transaction
*
* @return boolean
*/
protected function _beginNested() {
$query = 'SAVEPOINT LEVEL' . ++$this->_transactionNesting;
if ($this->fullDebug) {
$this->logQuery($query);
}
$this->_connection->exec($query);
return true;
}

/**
Expand All @@ -2046,19 +2080,38 @@ public function begin() {
* or a transaction has not started).
*/
public function commit() {
if ($this->_transactionStarted) {
$this->_transactionNesting--;
if ($this->_transactionNesting <= 0) {
$this->_transactionStarted = false;
$this->_transactionNesting = 0;
if ($this->fullDebug) {
$this->logQuery('COMMIT');
}
return $this->_connection->commit();
if (!$this->_transactionStarted) {
return false;
}

if ($this->_transactionNesting === 0) {
if ($this->fullDebug) {
$this->logQuery('COMMIT');
}
return true;
$this->_transactionStarted = false;
return $this->_connection->commit();
}
return false;

if ($this->supportNestedTransaction()) {
return $this->_commitNested();
}

$this->_transactionNesting--;
return true;
}

/**
* Commit a nested transaction
*
* @return boolean
*/
protected function _commitNested() {
$query = 'RELEASE SAVEPOINT LEVEL' . $this->_transactionNesting--;
if ($this->fullDebug) {
$this->logQuery($query);
}
$this->_connection->exec($query);
return true;
}

/**
Expand All @@ -2069,15 +2122,38 @@ public function commit() {
* or a transaction has not started).
*/
public function rollback() {
if ($this->_transactionStarted && $this->_connection->rollBack()) {
if (!$this->_transactionStarted) {
return false;
}

if ($this->_transactionNesting === 0) {
if ($this->fullDebug) {
$this->logQuery('ROLLBACK');
}
$this->_transactionStarted = false;
$this->_transactionNesting = 0;
return true;
return $this->_connection->rollBack();
}
return false;

if ($this->supportNestedTransaction()) {
return $this->_rollbackNested();
}

$this->_transactionNesting--;
return true;
}

/**
* Rollback a nested transaction
*
* @return boolean
*/
protected function _rollbackNested() {
$query = 'ROLLBACK TO SAVEPOINT LEVEL' . $this->_transactionNesting--;
if ($this->fullDebug) {
$this->logQuery($query);
}
$this->_connection->exec($query);
return true;
}

/**
Expand Down
36 changes: 35 additions & 1 deletion lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php
Expand Up @@ -45,7 +45,7 @@ class MysqlTest extends CakeTestCase {
public $fixtures = array(
'core.apple', 'core.article', 'core.articles_tag', 'core.attachment', 'core.comment',
'core.sample', 'core.tag', 'core.user', 'core.post', 'core.author', 'core.data_test',
'core.binary_test'
'core.binary_test', 'app.address'
);

/**
Expand Down Expand Up @@ -3579,4 +3579,38 @@ public function testTruncateStatements() {
->with("TRUNCATE TABLE `$schema`.`tbl_articles`");
$this->Dbo->truncate('articles');
}

/**
* Test nested transaction
*
* @return void
*/
public function testNestedTransaction() {
$this->skipIf($this->Dbo->supportNestedTransaction() === false, 'The MySQL server do not support nested transaction');

$this->loadFixtures('Address');
$model = ClassRegistry::init('Address');
$model->hasOne = $model->hasMany = $model->belongsTo = $model->hasAndBelongsToMany = array();
$model->cacheQueries = false;
$this->Dbo->cacheMethods = false;

$this->assertTrue($this->Dbo->begin());
$this->assertNotEmpty($model->read(null, 1));

$this->assertTrue($this->Dbo->begin());
$this->assertTrue($model->delete(1));
$this->assertEmpty($model->read(null, 1));
$this->assertTrue($this->Dbo->rollback());
$this->assertNotEmpty($model->read(null, 1));

$this->assertTrue($this->Dbo->begin());
$this->assertTrue($model->delete(1));
$this->assertEmpty($model->read(null, 1));
$this->assertTrue($this->Dbo->commit());
$this->assertEmpty($model->read(null, 1));

$this->assertTrue($this->Dbo->rollback());
$this->assertNotEmpty($model->read(null, 1));
}

}

0 comments on commit 7fe333c

Please sign in to comment.