From 2c9399a78aa6b272311a826bb75d141674bb9566 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 31 Mar 2012 21:58:33 -0400 Subject: [PATCH 01/10] Removed unused attribute and moved getVersion to be reused in all PDO drivers. --- lib/Cake/Model/Datasource/Database/Mysql.php | 20 ----------------- .../Model/Datasource/Database/Postgres.php | 11 ---------- .../Model/Datasource/Database/Sqlserver.php | 22 +------------------ lib/Cake/Model/Datasource/DboSource.php | 22 +++++++++---------- 4 files changed, 11 insertions(+), 64 deletions(-) diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index b5421f43d..17da3224b 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -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 * diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index 064a1cca4..29f6bbe2c 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -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. * diff --git a/lib/Cake/Model/Datasource/Database/Sqlserver.php b/lib/Cake/Model/Datasource/Database/Sqlserver.php index d7882663b..adea9beb1 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlserver.php +++ b/lib/Cake/Model/Datasource/Database/Sqlserver.php @@ -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]); diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 7e85cc4bb..39a90c382 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -183,17 +183,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 +283,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. * From 2c1cf29aa3fda70ca314e6af023333552b96ab6d Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 31 Mar 2012 22:39:18 -0400 Subject: [PATCH 02/10] Added real support to nested transactions for Mysql, Postgres, Sqlite. --- lib/Cake/Model/Datasource/Database/Mysql.php | 9 ++ .../Model/Datasource/Database/Postgres.php | 9 ++ lib/Cake/Model/Datasource/Database/Sqlite.php | 9 ++ lib/Cake/Model/Datasource/DboSource.php | 113 ++++++++++++++---- 4 files changed, 118 insertions(+), 22 deletions(-) diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index 17da3224b..ad618429a 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -676,4 +676,13 @@ class Mysql extends DboSource { return $this->config['database']; } +/** + * Check if the server support nested transactions + * + * @return boolean + */ + protected function _supportNestedTransaction() { + return version_compare($this->getVersion(), '4.1', '>='); + } + } diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index 29f6bbe2c..d681c8a64 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -895,4 +895,13 @@ class Postgres extends DboSource { return $this->config['schema']; } +/** + * Check if the server support nested transactions + * + * @return boolean + */ + protected function _supportNestedTransaction() { + return version_compare($this->getVersion(), '8.0', '>='); + } + } diff --git a/lib/Cake/Model/Datasource/Database/Sqlite.php b/lib/Cake/Model/Datasource/Database/Sqlite.php index 59db71dcf..3eee31a54 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlite.php +++ b/lib/Cake/Model/Datasource/Database/Sqlite.php @@ -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 + */ + protected function _supportNestedTransaction() { + return version_compare($this->getVersion(), '3.6.8', '>='); + } + } diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 39a90c382..47a6c2fd6 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -2017,6 +2017,15 @@ class DboSource extends DataSource { return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table)); } +/** + * Check if the server support nested transactions + * + * @return boolean + */ + protected function _supportNestedTransaction() { + return false; + } + /** * Begin a transaction * @@ -2025,15 +2034,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; } /** @@ -2044,19 +2071,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; } /** @@ -2067,15 +2113,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; } /** From 30258ac817e6494f3a75adc56710ec87b7bfa6dc Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 31 Mar 2012 23:14:49 -0400 Subject: [PATCH 03/10] Added test for nested methods sequence. --- .../Case/Model/Datasource/DboSourceTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index 6c043a119..61d37b6bf 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -51,6 +51,10 @@ class DboTestSource extends DboSource { $this->_connection = $conn; } + protected function _supportNestedTransaction() { + return true; + } + } /** @@ -841,6 +845,31 @@ 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); + + $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)); + + $db->begin(); + $db->begin(); + $db->commit(); + $db->begin(); + $db->rollback(); + $db->commit(); + } + /** * Test build statement with some fields missing * From 22cd65b7d89b89ca306db6a08edfef328f2cbeae Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 1 Apr 2012 00:01:11 -0400 Subject: [PATCH 04/10] Added tests in each datasource to test the nested transactions. --- .../Model/Datasource/Database/MysqlTest.php | 38 ++++++++++++++++++- .../Datasource/Database/PostgresTest.php | 35 +++++++++++++++++ .../Model/Datasource/Database/SqliteTest.php | 35 +++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php index 3024a0607..a91069a60 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php @@ -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,40 @@ class MysqlTest extends CakeTestCase { ->with("TRUNCATE TABLE `$schema`.`tbl_articles`"); $this->Dbo->truncate('articles'); } + +/** + * Test nested transaction + * + * @return void + */ + public function testNestedTransaction() { + $obj = new ReflectionMethod($this->Dbo, '_supportNestedTransaction'); + $obj->setAccessible(true); + $this->skipIf($obj->invoke($this->Dbo) === 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)); + } + } diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php index 54b331f48..4a6b7897e 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php @@ -909,4 +909,39 @@ class PostgresTest extends CakeTestCase { $this->Dbo->truncate('articles'); } +/** + * Test nested transaction + * + * @return void + */ + public function testNestedTransaction() { + $obj = new ReflectionMethod($this->Dbo, '_supportNestedTransaction'); + $obj->setAccessible(true); + $this->skipIf($obj->invoke($this->Dbo) === 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)); + } + } diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php index f53257bff..1e4ed7e46 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php @@ -383,4 +383,39 @@ class SqliteTest extends CakeTestCase { $this->assertTrue(Validation::uuid($result['Uuid']['id']), 'Not a uuid'); } +/** + * Test nested transaction + * + * @return void + */ + public function testNestedTransaction() { + $obj = new ReflectionMethod($this->Dbo, '_supportNestedTransaction'); + $obj->setAccessible(true); + $this->skipIf($obj->invoke($this->Dbo) === 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)); + } + } From ffe0a1849582253432fc8aea6d4ee4b39b583dc2 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 1 Apr 2012 00:07:39 -0400 Subject: [PATCH 05/10] ReflectionMethod::setAccessible() is not compatible with PHP 5.2, causing fatal error in CI. Assuming that the db servers support nested transaction. --- lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php | 4 ---- lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php | 4 ---- lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php | 4 ---- 3 files changed, 12 deletions(-) diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php index a91069a60..bb7bc4c67 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php @@ -3586,10 +3586,6 @@ class MysqlTest extends CakeTestCase { * @return void */ public function testNestedTransaction() { - $obj = new ReflectionMethod($this->Dbo, '_supportNestedTransaction'); - $obj->setAccessible(true); - $this->skipIf($obj->invoke($this->Dbo) === 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(); diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php index 4a6b7897e..68e0c3280 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php @@ -915,10 +915,6 @@ class PostgresTest extends CakeTestCase { * @return void */ public function testNestedTransaction() { - $obj = new ReflectionMethod($this->Dbo, '_supportNestedTransaction'); - $obj->setAccessible(true); - $this->skipIf($obj->invoke($this->Dbo) === false, 'The Postgres server do not support nested transaction'); - $this->loadFixtures('Article'); $model = new Article(); $model->hasOne = $model->hasMany = $model->belongsTo = $model->hasAndBelongsToMany = array(); diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php index 1e4ed7e46..49f99756b 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php @@ -389,10 +389,6 @@ class SqliteTest extends CakeTestCase { * @return void */ public function testNestedTransaction() { - $obj = new ReflectionMethod($this->Dbo, '_supportNestedTransaction'); - $obj->setAccessible(true); - $this->skipIf($obj->invoke($this->Dbo) === false, 'The Sqlite version do not support nested transaction'); - $this->loadFixtures('User'); $model = new User(); $model->hasOne = $model->hasMany = $model->belongsTo = $model->hasAndBelongsToMany = array(); From a512d466795380600a0223088f6b0d10605b936c Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 1 Apr 2012 00:21:12 -0400 Subject: [PATCH 06/10] Sqlite is failing in PHP 5.2, re-adding the check for driver support. --- lib/Cake/Model/Datasource/Database/Mysql.php | 2 +- lib/Cake/Model/Datasource/Database/Postgres.php | 2 +- lib/Cake/Model/Datasource/Database/Sqlite.php | 2 +- lib/Cake/Model/Datasource/DboSource.php | 8 ++++---- .../Test/Case/Model/Datasource/Database/MysqlTest.php | 2 ++ .../Test/Case/Model/Datasource/Database/PostgresTest.php | 2 ++ .../Test/Case/Model/Datasource/Database/SqliteTest.php | 2 ++ 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index ad618429a..b94ece767 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -681,7 +681,7 @@ class Mysql extends DboSource { * * @return boolean */ - protected function _supportNestedTransaction() { + public function supportNestedTransaction() { return version_compare($this->getVersion(), '4.1', '>='); } diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index d681c8a64..261a06d6c 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -900,7 +900,7 @@ class Postgres extends DboSource { * * @return boolean */ - protected function _supportNestedTransaction() { + public function supportNestedTransaction() { return version_compare($this->getVersion(), '8.0', '>='); } diff --git a/lib/Cake/Model/Datasource/Database/Sqlite.php b/lib/Cake/Model/Datasource/Database/Sqlite.php index 3eee31a54..cbc2caeaa 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlite.php +++ b/lib/Cake/Model/Datasource/Database/Sqlite.php @@ -564,7 +564,7 @@ class Sqlite extends DboSource { * * @return boolean */ - protected function _supportNestedTransaction() { + public function supportNestedTransaction() { return version_compare($this->getVersion(), '3.6.8', '>='); } diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 47a6c2fd6..6c7f37a47 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -2022,7 +2022,7 @@ class DboSource extends DataSource { * * @return boolean */ - protected function _supportNestedTransaction() { + public function supportNestedTransaction() { return false; } @@ -2035,7 +2035,7 @@ class DboSource extends DataSource { */ public function begin() { if ($this->_transactionStarted) { - if ($this->_supportNestedTransaction()) { + if ($this->supportNestedTransaction()) { return $this->_beginNested(); } $this->_transactionNesting++; @@ -2083,7 +2083,7 @@ class DboSource extends DataSource { return $this->_connection->commit(); } - if ($this->_supportNestedTransaction()) { + if ($this->supportNestedTransaction()) { return $this->_commitNested(); } @@ -2125,7 +2125,7 @@ class DboSource extends DataSource { return $this->_connection->rollBack(); } - if ($this->_supportNestedTransaction()) { + if ($this->supportNestedTransaction()) { return $this->_rollbackNested(); } diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php index bb7bc4c67..9fa5f9bd4 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php @@ -3586,6 +3586,8 @@ class MysqlTest extends CakeTestCase { * @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(); diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php index 68e0c3280..eb249e4d3 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php @@ -915,6 +915,8 @@ class PostgresTest extends CakeTestCase { * @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(); diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php index 49f99756b..a5a82cae1 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php @@ -389,6 +389,8 @@ class SqliteTest extends CakeTestCase { * @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(); From 79f7ca7d33c0efb828604954fbc820418036f3dc Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 1 Apr 2012 00:25:10 -0400 Subject: [PATCH 07/10] Oops, forgot one change in last commit. --- lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index 61d37b6bf..fffdb9f4c 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -51,7 +51,7 @@ class DboTestSource extends DboSource { $this->_connection = $conn; } - protected function _supportNestedTransaction() { + public function supportNestedTransaction() { return true; } From b0a3a1a5aa6b86fe449b41bdf4a23ac37e69a139 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 1 Apr 2012 00:41:16 -0400 Subject: [PATCH 08/10] Added test to check when driver do not support nested support. --- .../Case/Model/Datasource/DboSourceTest.php | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index fffdb9f4c..5a2cd8549 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -35,6 +35,8 @@ class MockDataSource extends DataSource { class DboTestSource extends DboSource { + public static $nested = true; + public function connect($config = array()) { $this->connected = true; } @@ -52,7 +54,7 @@ class DboTestSource extends DboSource { } public function supportNestedTransaction() { - return true; + return self::$nested; } } @@ -854,6 +856,7 @@ class DboSourceTest extends CakeTestCase { $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)); @@ -870,6 +873,29 @@ class DboSourceTest extends CakeTestCase { $db->commit(); } +/** + * Test nested transaction calls without support + * + * @return void + */ + public function testTransactionNestedWithoutSupport() { + $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)); + + $db->begin(); + $db->begin(); + $db->commit(); + $db->begin(); + $db->rollback(); + $db->commit(); + } + /** * Test build statement with some fields missing * From 7be5349b0cf47c03b39a455a2d3cd9a4404777ff Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 14 Apr 2012 16:41:08 -0400 Subject: [PATCH 09/10] Added configuration to disable nested transaction, even if the db supports it. --- lib/Cake/Model/Datasource/Database/Mysql.php | 2 +- .../Model/Datasource/Database/Postgres.php | 2 +- lib/Cake/Model/Datasource/Database/Sqlite.php | 2 +- lib/Cake/Model/Datasource/DboSource.php | 9 +++++++ .../Case/Model/Datasource/DboSourceTest.php | 26 ++++++++++++++++++- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index b94ece767..70c51366c 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -682,7 +682,7 @@ class Mysql extends DboSource { * @return boolean */ public function supportNestedTransaction() { - return version_compare($this->getVersion(), '4.1', '>='); + return $this->nestedTransaction && version_compare($this->getVersion(), '4.1', '>='); } } diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index 261a06d6c..35f568aad 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -901,7 +901,7 @@ class Postgres extends DboSource { * @return boolean */ public function supportNestedTransaction() { - return version_compare($this->getVersion(), '8.0', '>='); + return $this->nestedTransaction && version_compare($this->getVersion(), '8.0', '>='); } } diff --git a/lib/Cake/Model/Datasource/Database/Sqlite.php b/lib/Cake/Model/Datasource/Database/Sqlite.php index cbc2caeaa..419709892 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlite.php +++ b/lib/Cake/Model/Datasource/Database/Sqlite.php @@ -565,7 +565,7 @@ class Sqlite extends DboSource { * @return boolean */ public function supportNestedTransaction() { - return version_compare($this->getVersion(), '3.6.8', '>='); + return $this->nestedTransaction && version_compare($this->getVersion(), '3.6.8', '>='); } } diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 6c7f37a47..a37709df9 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -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? * diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index 5a2cd8549..647c373c4 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -54,7 +54,7 @@ class DboTestSource extends DboSource { } public function supportNestedTransaction() { - return self::$nested; + return $this->nestedTransaction && self::$nested; } } @@ -879,6 +879,30 @@ class DboSourceTest extends CakeTestCase { * @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)); + + $db->begin(); + $db->begin(); + $db->commit(); + $db->begin(); + $db->rollback(); + $db->commit(); + } + +/** + * Test nested transaction disabled + * + * @return void + */ + public function testTransactionNestedDisabled() { $conn = $this->getMock('MockPDO'); $db = new DboTestSource(); $db->setConnection($conn); From 37157806b920eddcc68bef63fad12d904942ce76 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 14 Apr 2012 16:46:47 -0400 Subject: [PATCH 10/10] Removing some dup code in nested transaction tests. --- .../Case/Model/Datasource/DboSourceTest.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index 647c373c4..7824a7ef2 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -865,12 +865,7 @@ class DboSourceTest extends CakeTestCase { $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)); - $db->begin(); - $db->begin(); - $db->commit(); - $db->begin(); - $db->rollback(); - $db->commit(); + $this->_runTransactions($db); } /** @@ -889,12 +884,7 @@ class DboSourceTest extends CakeTestCase { $conn->expects($this->never())->method('exec'); $conn->expects($this->once())->method('commit')->will($this->returnValue(true)); - $db->begin(); - $db->begin(); - $db->commit(); - $db->begin(); - $db->rollback(); - $db->commit(); + $this->_runTransactions($db); } /** @@ -912,6 +902,16 @@ class DboSourceTest extends CakeTestCase { $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();