Merge branch '2.2-nested-transaction' into 2.2

This commit is contained in:
Juan Basso 2012-04-22 23:04:32 -04:00
commit 7fe333ce4e
9 changed files with 318 additions and 87 deletions

View file

@ -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
*
@ -262,15 +251,6 @@ class Mysql extends DboSource {
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
*
@ -696,4 +676,13 @@ class Mysql extends DboSource {
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', '>=');
}
}

View file

@ -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.
*
@ -906,4 +895,13 @@ class Postgres extends DboSource {
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', '>=');
}
}

View file

@ -559,4 +559,13 @@ class Sqlite extends DboSource {
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', '>=');
}
}

View file

@ -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.
*
@ -151,7 +132,6 @@ class Sqlserver extends DboSource {
throw new MissingConnectionException(array('class' => $e->getMessage()));
}
$this->_version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
return $this->connected;
}
@ -515,7 +495,7 @@ class Sqlserver extends DboSource {
}
// 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]);

View file

@ -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?
*
@ -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
*
@ -294,12 +292,21 @@ class DboSource extends DataSource {
/**
* 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.
*
@ -2019,6 +2026,15 @@ class DboSource extends DataSource {
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
*
@ -2027,15 +2043,33 @@ class DboSource extends DataSource {
* 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;
}
/**
@ -2046,19 +2080,38 @@ class DboSource extends DataSource {
* 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();
}
return true;
if (!$this->_transactionStarted) {
return false;
}
return false;
if ($this->_transactionNesting === 0) {
if ($this->fullDebug) {
$this->logQuery('COMMIT');
}
$this->_transactionStarted = false;
return $this->_connection->commit();
}
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;
}
/**
@ -2069,15 +2122,38 @@ class DboSource extends DataSource {
* 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;
}
/**

View file

@ -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'
);
/**
@ -3579,4 +3579,38 @@ class MysqlTest extends CakeTestCase {
->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));
}
}

View file

@ -909,4 +909,37 @@ class PostgresTest extends CakeTestCase {
$this->Dbo->truncate('articles');
}
/**
* Test nested transaction
*
* @return void
*/
public function testNestedTransaction() {
$this->skipIf($this->Dbo->supportNestedTransaction() === false, 'The Postgres server do not support nested transaction');
$this->loadFixtures('Article');
$model = new Article();
$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));
}
}

View file

@ -383,4 +383,37 @@ class SqliteTest extends CakeTestCase {
$this->assertTrue(Validation::uuid($result['Uuid']['id']), 'Not a uuid');
}
/**
* Test nested transaction
*
* @return void
*/
public function testNestedTransaction() {
$this->skipIf($this->Dbo->supportNestedTransaction() === false, 'The Sqlite version do not support nested transaction');
$this->loadFixtures('User');
$model = new User();
$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));
}
}

View file

@ -35,6 +35,8 @@ class MockDataSource extends DataSource {
class DboTestSource extends DboSource {
public static $nested = true;
public function connect($config = array()) {
$this->connected = true;
}
@ -51,6 +53,10 @@ class DboTestSource extends DboSource {
$this->_connection = $conn;
}
public function supportNestedTransaction() {
return $this->nestedTransaction && self::$nested;
}
}
/**
@ -834,6 +840,79 @@ class DboSourceTest extends CakeTestCase {
$this->assertEquals($expected, $log['log'][0]);
}
/**
* Test nested transaction calls
*
* @return void
*/
public function testTransactionNested() {
$conn = $this->getMock('MockPDO');
$db = new DboTestSource();
$db->setConnection($conn);
DboTestSource::$nested = true;
$conn->expects($this->at(0))->method('beginTransaction')->will($this->returnValue(true));
$conn->expects($this->at(1))->method('exec')->with($this->equalTo('SAVEPOINT LEVEL1'))->will($this->returnValue(true));
$conn->expects($this->at(2))->method('exec')->with($this->equalTo('RELEASE SAVEPOINT LEVEL1'))->will($this->returnValue(true));
$conn->expects($this->at(3))->method('exec')->with($this->equalTo('SAVEPOINT LEVEL1'))->will($this->returnValue(true));
$conn->expects($this->at(4))->method('exec')->with($this->equalTo('ROLLBACK TO SAVEPOINT LEVEL1'))->will($this->returnValue(true));
$conn->expects($this->at(5))->method('commit')->will($this->returnValue(true));
$this->_runTransactions($db);
}
/**
* Test nested transaction calls without support
*
* @return void
*/
public function testTransactionNestedWithoutSupport() {
$conn = $this->getMock('MockPDO');
$db = new DboTestSource();
$db->setConnection($conn);
$db->nestedTransaction = false;
DboTestSource::$nested = true;
$conn->expects($this->once())->method('beginTransaction')->will($this->returnValue(true));
$conn->expects($this->never())->method('exec');
$conn->expects($this->once())->method('commit')->will($this->returnValue(true));
$this->_runTransactions($db);
}
/**
* Test nested transaction disabled
*
* @return void
*/
public function testTransactionNestedDisabled() {
$conn = $this->getMock('MockPDO');
$db = new DboTestSource();
$db->setConnection($conn);
DboTestSource::$nested = false;
$conn->expects($this->once())->method('beginTransaction')->will($this->returnValue(true));
$conn->expects($this->never())->method('exec');
$conn->expects($this->once())->method('commit')->will($this->returnValue(true));
$this->_runTransactions($db);
}
/**
* Nested transaction calls
*
* @param DboTestSource $db
* @return void
*/
protected function _runTransactions($db) {
$db->begin();
$db->begin();
$db->commit();
$db->begin();
$db->rollback();
$db->commit();
}
/**
* Test build statement with some fields missing
*