diff --git a/core/DB/DB.class.php b/core/DB/DB.class.php index 3477408563..4d7775aa3c 100644 --- a/core/DB/DB.class.php +++ b/core/DB/DB.class.php @@ -35,6 +35,7 @@ abstract class DB * flag to indicate whether we're in transaction **/ private $transaction = false; + private $transactionList = array(); private $queue = array(); private $toQueue = false; @@ -65,6 +66,9 @@ public function __destruct() } } + /** + * @return Dialect + */ public static function getDialect() { throw new UnimplementedFeatureException('implement me, please'); @@ -111,20 +115,27 @@ public function begin( /* AccessMode */ $mode = null ) { - $begin = 'begin'; - - if ($level && $level instanceof IsolationLevel) - $begin .= ' '.$level->toString(); - - if ($mode && $mode instanceof AccessMode) - $begin .= ' '.$mode->toString(); + if (!$this->transaction) { + $begin = 'begin'; - if ($this->toQueue) - $this->queue[] = $begin; - else - $this->queryRaw("{$begin};\n"); - - $this->transaction = true; + if ($level && $level instanceof IsolationLevel) + $begin .= ' '.$level->toString(); + + if ($mode && $mode instanceof AccessMode) + $begin .= ' '.$mode->toString(); + + if ($this->toQueue) + $this->queue[] = $begin; + else + $this->queryRaw("{$begin};\n"); + + $this->transaction = true; + } else { + $deep = count($this->transactionList) + 1; + $savepointName = 'onPHP_inTransaction'.$deep; + $this->savePoint($savepointName); + array_push($this->transactionList, $savepointName); + } return $this; } @@ -134,12 +145,16 @@ public function begin( **/ public function commit() { - if ($this->toQueue) - $this->queue[] = 'commit;'; - else - $this->queryRaw("commit;\n"); - - $this->transaction = false; + if (empty($this->transactionList)) { + if ($this->toQueue) + $this->queue[] = 'commit;'; + else + $this->queryRaw("commit;\n"); + $this->transaction = false; + } else { + $savepointName = array_pop($this->transactionList); + $this->releaseSavepoint($savepointName); + } return $this; } @@ -149,12 +164,17 @@ public function commit() **/ public function rollback() { - if ($this->toQueue) - $this->queue[] = 'rollback;'; - else - $this->queryRaw("rollback;\n"); - - $this->transaction = false; + if (empty($this->transactionList)) { + if ($this->toQueue) + $this->queue[] = 'rollback;'; + else + $this->queryRaw("rollback;\n"); + + $this->transaction = false; + } else { + $savepointName = array_pop($this->transactionList); + $this->rollbackToSavepoint($savepointName); + } return $this; } @@ -165,6 +185,54 @@ public function inTransaction() } //@} + /** + * @param string $savePoint + * @return DB + **/ + public function savePoint($savePoint) + { +// $savePoint = $this->getDialect()->toFieldString($savePoint); + $savepoint = "savepoint {$savePoint};"; + if ($this->toQueue) + $this->queue[] = $savepoint; + else + $this->queryRaw("{$savepoint}\n"); + + return $this; + } + + /** + * @param string $savePoint + * @return DB + **/ + public function releaseSavepoint($savePoint) + { +// $savePoint = $this->getDialect()->toFieldString($savePoint); + $releaseSavepoint = "release savepoint {$savePoint};"; + if ($this->toQueue) + $this->queue[] = $releaseSavepoint; + else + $this->queryRaw("{$releaseSavepoint}\n"); + + return $this; + } + + /** + * @param string $savePoint + * @return DB + **/ + public function rollbackToSavepoint($savePoint) + { +// $savePoint = $this->getDialect()->toFieldString($savePoint); + $releaseSavepoint = "rollback to savepoint {$savePoint};"; + if ($this->toQueue) + $this->queue[] = $releaseSavepoint; + else + $this->queryRaw("{$releaseSavepoint}\n"); + + return $this; + } + /** * queue handling * @deprecated by Queue class diff --git a/core/OSQL/CreateTableQuery.class.php b/core/OSQL/CreateTableQuery.class.php index 0af49d9388..70a32c709b 100644 --- a/core/OSQL/CreateTableQuery.class.php +++ b/core/OSQL/CreateTableQuery.class.php @@ -79,7 +79,12 @@ public function toDialectString(Dialect $dialect) } } - return $out."\n);\n"; + $out .= "\n)"; + if ($dialect instanceof MyDialect) { + $out .= ' TYPE=innodb '; + } + + return $out.";\n"; } } ?> \ No newline at end of file diff --git a/test/AllTests.php b/test/AllTests.php index 7ba2e66e3f..a69415d9ff 100644 --- a/test/AllTests.php +++ b/test/AllTests.php @@ -127,6 +127,7 @@ public static function suite() } $suite->addTestSuite('DAOTest'); + $suite->addTestSuite('TransactionTest'); return $suite; } diff --git a/test/misc/TransactionTest.class.php b/test/misc/TransactionTest.class.php new file mode 100644 index 0000000000..51d9988f90 --- /dev/null +++ b/test/misc/TransactionTest.class.php @@ -0,0 +1,140 @@ +schema-> + getTableByName('test_parent_object')-> + getColumnByName('root_id')-> + dropReference(); + + return parent::create(); + } + + public function testData() + { + $this->create(); + + foreach (DBTestPool::me()->getPool() as $connector => $db) { + DBPool::me()->setDefault($db); + $this->simpleTransaction(); + + $this->innerTransactions(); + } + + $this->drop(); + } + + private function simpleTransaction() + { + $idList = $this->insertAndCommit(); + $idList += $this->insertAndRollback(); + } + + private function innerTransactions() + { + $dao = TestItem::dao(); + + $idList = $this->externalCommit(); + $idList = $this->externalRollback(); + + $itemList = $dao->getListByIds($idList); + $this->assertEquals(count($idList), count($itemList)); + } + + /** + * @return array + **/ + private function externalCommit() + { + $dao = TestItem::dao(); + $db = DBPool::getByDao($dao); + $db->begin(); + + $idList = $this->insertAndCommit(); + $idList += $this->insertAndRollback(); + + $db->commit(); + + $itemList = $dao->getListByIds($idList); + $this->assertEquals(count($idList), count($itemList)); + + return $idList; + } + + /** + * @return array + **/ + private function externalRollback() + { + $dao = TestItem::dao(); + $db = DBPool::getByDao($dao); + $db->begin(); + + $idList = $this->insertAndCommit(); + $idList += $this->insertAndRollback(); + + $db->rollback(); + $dao->uncacheByIds($idList); + + $itemList = $dao->getListByIds($idList); + $this->assertTrue(empty($itemList)); + + return array(); + } + + /** + * @return array + **/ + private function insertAndCommit() + { + $dao = TestItem::dao(); + $db = DBPool::getByDao($dao); + $db->begin(); + + $item = $dao->add(TestItem::create()->setName('someItem')); + $itemId = $item->getId(); + + $db->commit(); + + try { + $dao->getById($itemId); + } catch (ObjectNotFoundException $e) { + $this->fail('Object must be saved'); + } + + return array($itemId); + } + + /** + * @return array + **/ + private function insertAndRollback() + { + $dao = TestItem::dao(); + $db = DBPool::getByDao($dao); + $db->begin(); + + $item = $dao->add(TestItem::create()->setName('someItem')); + $itemId = $item->getId(); + + $db->rollback(); + $dao->uncacheById($itemId); + + try { + $dao->getById($itemId); + $this->fail('Object must not be saved'); + } catch (ObjectNotFoundException $e) { + /* all ok */ + } + + return array(); + } + } +?> \ No newline at end of file