Commit 6abf6aaf authored by Benjamin Eberlei's avatar Benjamin Eberlei

Merge remote branch 'lsmith77/savepoints' into DBAL-55

parents 3d55dc8e f482ce3f
......@@ -101,6 +101,13 @@ class Connection implements DriverConnection
*/
private $_transactionIsolationLevel;
/**
* If nested transations should use savepoints
*
* @var integer
*/
private $_nestTransactionsWithSavepoints;
/**
* The parameters used during creation of the Connection instance.
*
......@@ -740,6 +747,48 @@ class Connection implements DriverConnection
}
}
/**
* Set if nested transactions should use savepoints
*
* @param boolean
*/
public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
{
if ($this->_transactionNestingLevel > 0) {
throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
}
if ($nestTransactionsWithSavepoints && !$this->_platform->supportsSavepoints()) {
ConnectionException::savepointsNotSupported();
}
$this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints;
}
/**
* Get if nested transactions should use savepoints
*
* @return boolean
*/
public function getNestTransactionsWithSavepoints()
{
return $this->_nestTransactionsWithSavepoints;
}
/**
* Returns the savepoint name to use for nested transactions are false if they are not supported
* "savepointFormat" parameter is not set
*
* @return mixed a string with the savepoint name or false
*/
protected function _getNestedTransactionSavePointName() {
if ($this->_platform->supportsSavepoints() && !empty($this->_nestTransactionsWithSavepoints)) {
return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
}
return false;
}
/**
* Starts a transaction by suspending auto-commit mode.
*
......@@ -749,11 +798,16 @@ class Connection implements DriverConnection
{
$this->connect();
if ($this->_transactionNestingLevel == 0) {
++$this->_transactionNestingLevel;
if ($this->_transactionNestingLevel == 1) {
$this->_conn->beginTransaction();
} else {
$savepointName = $this->_getNestedTransactionSavePointName($this->_transactionNestingLevel);
if ($savepointName) {
$this->createSavePoint($savepointName);
}
}
++$this->_transactionNestingLevel;
}
/**
......@@ -776,6 +830,11 @@ class Connection implements DriverConnection
if ($this->_transactionNestingLevel == 1) {
$this->_conn->commit();
} else {
$savepointName = $this->_getNestedTransactionSavePointName($this->_transactionNestingLevel);
if ($savepointName && $this->_platform->supportsReleaseSavepoints()) {
$this->releaseSavePoint($savepointName);
}
}
--$this->_transactionNestingLevel;
......@@ -802,11 +861,66 @@ class Connection implements DriverConnection
$this->_conn->rollback();
$this->_isRollbackOnly = false;
} else {
$this->_isRollbackOnly = true;
$savepointName = $this->_getNestedTransactionSavePointName($this->_transactionNestingLevel);
if (!$this->_isRollbackOnly && $savepointName) {
$this->rollbackSavePoint($savepointName);
} else {
$this->_isRollbackOnly = true;
}
--$this->_transactionNestingLevel;
}
}
/**
* createSavepoint
* creates a new savepoint
*
* @param string $savepoint name of a savepoint to set
* @return void
*/
public function createSavePoint($savepoint)
{
if (!$this->_platform->supportsSavepoints()) {
ConnectionException::savepointsNotSupported();
}
$this->_conn->exec($this->_platform->createSavePoint($savepoint));
}
/**
* releaseSavePoint
* releases given savepoint
*
* @param string $savepoint name of a savepoint to release
* @return void
*/
public function releaseSavePoint($savepoint)
{
if (!$this->_platform->supportsSavepoints()) {
ConnectionException::savepointsNotSupported();
}
if ($this->_platform->supportsReleaseSavepoints()) {
$this->_conn->exec($this->_platform->releaseSavePoint($savepoint));
}
}
/**
* rollbackSavePoint
* releases given savepoint
*
* @param string $savepoint name of a savepoint to rollback to
* @return void
*/
public function rollbackSavePoint($savepoint)
{
if (!$this->_platform->supportsSavepoints()) {
ConnectionException::savepointsNotSupported();
}
$this->_conn->exec($this->_platform->rollbackSavePoint($savepoint));
}
/**
* Gets the wrapped driver connection.
*
......
......@@ -41,4 +41,14 @@ class ConnectionException extends DBALException
{
return new self("There is no active transaction.");
}
public static function savepointsNotSupported()
{
return new self("Savepoints are not supported transaction.");
}
public static function mayNotAlterNestedTransactionWithSavepointsInTransaction()
{
return new self("May not alter the nested transaction with savepoints behavior while a transaction is open.");
}
}
\ No newline at end of file
......@@ -1773,6 +1773,16 @@ abstract class AbstractPlatform
return true;
}
/**
* Whether the platform supports releasing savepoints.
*
* @return boolean
*/
public function supportsReleaseSavepoints()
{
return $this->supportsSavepoints();
}
/**
* Whether the platform supports primary key constraints.
*
......@@ -1986,4 +1996,37 @@ abstract class AbstractPlatform
{
return 'SELECT 1';
}
/**
* Generate SQL to create a new savepoint
*
* @param string $savepoint
* @return string
*/
public function createSavePoint($savepoint)
{
return 'SAVEPOINT ' . $savepoint;
}
/**
* Generate SQL to release a savepoint
*
* @param string $savepoint
* @return string
*/
public function releaseSavePoint($savepoint)
{
return 'RELEASE SAVEPOINT ' . $savepoint;
}
/**
* Generate SQL to rollback a savepoint
*
* @param string $savepoint
* @return string
*/
public function rollbackSavePoint($savepoint)
{
return 'ROLLBACK TO SAVEPOINT ' . $savepoint;
}
}
\ No newline at end of file
......@@ -291,6 +291,16 @@ class DB2Platform extends AbstractPlatform
return false;
}
/**
* Whether the platform supports releasing savepoints.
*
* @return boolean
*/
public function supportsReleaseSavepoints()
{
return false;
}
/**
* Gets the SQL specific for the platform to get the current date.
*
......
......@@ -61,12 +61,11 @@ class MsSqlPlatform extends AbstractPlatform
}
/**
* Whether the platform supports savepoints. MsSql does not.
* Whether the platform supports releasing savepoints.
*
* @return boolean
* @override
*/
public function supportsSavepoints()
public function supportsReleaseSavepoints()
{
return false;
}
......@@ -92,7 +91,7 @@ class MsSqlPlatform extends AbstractPlatform
*/
public function getDropDatabaseSQL($name)
{
return 'ALTER DATABASE [' . $name . ']
return 'ALTER DATABASE [' . $name . ']
SET SINGLE_USER --or RESTRICTED_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE ' . $name . ';';
......@@ -121,37 +120,36 @@ DROP DATABASE ' . $name . ';';
return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
}
/**
/**
* @override
*/
public function getDropIndexSQL($index, $table=null)
public function getDropIndexSQL($index, $table=null)
{
if($index instanceof \Doctrine\DBAL\Schema\Index) {
$index_ = $index;
$index = $index->getName();
} else if(!is_string($index)) {
throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
}
if (!isset($table)) {
return 'DROP INDEX ' . $index;
} else {
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
ALTER TABLE " . $this->quoteIdentifier($table) . " DROP CONSTRAINT " . $this->quoteIdentifier($index) . "
ELSE
DROP INDEX " . $this->quoteIdentifier($index) . " ON " . $this->quoteIdentifier($table);
}
if (!isset($table)) {
return 'DROP INDEX ' . $index;
} else {
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
ALTER TABLE " . $this->quoteIdentifier($table) . " DROP CONSTRAINT " . $this->quoteIdentifier($index) . "
ELSE
DROP INDEX " . $this->quoteIdentifier($index) . " ON " . $this->quoteIdentifier($table);
}
}
/**
/**
* @override
*/
protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
{
$columnListSql = $this->getColumnDeclarationListSQL($columns);
......@@ -174,8 +172,8 @@ DROP DATABASE ' . $name . ';';
$query .= ')';
$sql[] = $query;
if (isset($options['indexes']) && ! empty($options['indexes'])) {
if (isset($options['indexes']) && ! empty($options['indexes'])) {
foreach ($options['indexes'] AS $index) {
$sql[] = $this->getCreateIndexSQL($index, $tableName);
}
......@@ -222,9 +220,9 @@ DROP DATABASE ' . $name . ';';
$sql = array();
foreach ($queryParts as $query) {
$sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
}
foreach ($queryParts as $query) {
$sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
}
$sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
......@@ -291,24 +289,24 @@ DROP DATABASE ' . $name . ';';
{
return "exec sp_helpindex '" . $table . "'";
}
/**
/**
* @override
*/
public function getCreateViewSQL($name, $sql)
public function getCreateViewSQL($name, $sql)
{
return 'CREATE VIEW ' . $name . ' AS ' . $sql;
}
/**
/**
* @override
*/
public function getListViewsSQL($database)
public function getListViewsSQL($database)
{
return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name";
}
/**
/**
* @override
*/
public function getDropViewSQL($name)
......@@ -386,8 +384,8 @@ DROP DATABASE ' . $name . ';';
$args = func_get_args();
return '(' . implode(' + ', $args) . ')';
}
public function getListDatabasesSQL()
public function getListDatabasesSQL()
{
return 'SELECT * FROM SYS.DATABASES';
}
......@@ -644,7 +642,7 @@ DROP DATABASE ' . $name . ';';
{
return 'Y-m-d H:i:s.u';
}
/**
* @override
*/
......@@ -667,4 +665,37 @@ DROP DATABASE ' . $name . ';';
{
}
/**
* Generate SQL to create a new savepoint
*
* @param string $savepoint
* @return string
*/
public function createSavePoint($savepoint)
{
return 'SAVE TRANSACTION ' . $savepoint;
}
/**
* Generate SQL to release a savepoint
*
* @param string $savepoint
* @return string
*/
public function releaseSavePoint($savepoint)
{
return '';
}
/**
* Generate SQL to rollback a savepoint
*
* @param string $savepoint
* @return string
*/
public function rollbackSavePoint($savepoint)
{
return 'ROLLBACK TRANSACTION ' . $savepoint;
}
}
......@@ -258,17 +258,6 @@ class MySqlPlatform extends AbstractPlatform
{
return true;
}
/**
* Whether the platform supports savepoints. MySql does not.
*
* @return boolean
* @override
*/
public function supportsSavepoints()
{
return false;
}
public function getShowDatabasesSQL()
{
......
......@@ -659,6 +659,16 @@ LEFT JOIN all_cons_columns r_cols
return false;
}
/**
* Whether the platform supports releasing savepoints.
*
* @return boolean
*/
public function supportsReleaseSavepoints()
{
return false;
}
/**
* @inheritdoc
*/
......@@ -700,4 +710,15 @@ LEFT JOIN all_cons_columns r_cols
'urowid' => 'string'
);
}
/**
* Generate SQL to release a savepoint
*
* @param string $savepoint
* @return string
*/
public function releaseSavePoint($savepoint)
{
return '';
}
}
......@@ -44,7 +44,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
//no rethrow
}
$this->assertTrue($this->_conn->isRollbackOnly());
$this->_conn->commit(); // should throw exception
$this->fail('Transaction commit after failed nested transaction should fail.');
} catch (ConnectionException $e) {
......@@ -53,7 +53,44 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->assertEquals(0, $this->_conn->getTransactionNestingLevel());
}
}
public function testTransactionNestingBehaviorWithSavepoints()
{
if ($this->_conn->getDatabasePlatform()->supportsSavepoints()) {
$this->_conn->setNestTransactionsWithSavepoints(true);
try {
$this->_conn->beginTransaction();
$this->assertEquals(1, $this->_conn->getTransactionNestingLevel());
try {
$this->_conn->beginTransaction();
$this->assertEquals(2, $this->_conn->getTransactionNestingLevel());
$this->_conn->beginTransaction();
$this->assertEquals(3, $this->_conn->getTransactionNestingLevel());
$this->_conn->commit();
$this->assertEquals(2, $this->_conn->getTransactionNestingLevel());
throw new \Exception;
$this->_conn->commit(); // never reached
} catch (\Exception $e) {
$this->_conn->rollback();
$this->assertEquals(1, $this->_conn->getTransactionNestingLevel());
//no rethrow
}
$this->assertFalse($this->_conn->isRollbackOnly());
try {
$this->_conn->setNestTransactionsWithSavepoints(false);
$this->fail('Should not be able to disable savepoints in usage for nested transactions inside an open transaction.');
} catch (ConnectionException $e) {
$this->assertTrue($this->_conn->getNestTransactionsWithSavepoints());
}
$this->_conn->commit(); // should not throw exception
} catch (ConnectionException $e) {
$this->fail('Transaction commit after failed nested transaction should not fail when using savepoints.');
$this->_conn->rollback();
}
}
}
public function testTransactionBehaviorWithRollback()
{
try {
......
......@@ -23,14 +23,14 @@ class MsSqlPlatformTest extends AbstractPlatformTestCase
{
return array(
'CREATE TABLE test (foo VARCHAR(255) DEFAULT NULL, bar VARCHAR(255) DEFAULT NULL)',
'CREATE UNIQUE INDEX test_foo_bar_uniq ON test (foo, bar)'
'CREATE UNIQUE INDEX test_foo_bar_uniq ON test (foo, bar)'
);
}
public function getGenerateAlterTableSql()
{
return array(
'ALTER TABLE mytable RENAME TO userlist',
'ALTER TABLE mytable RENAME TO userlist',
'ALTER TABLE mytable ADD quota INT DEFAULT NULL',
'ALTER TABLE mytable DROP COLUMN foo',
'ALTER TABLE mytable CHANGE bar baz VARCHAR(255) DEFAULT \'def\' NOT NULL',
......@@ -127,7 +127,7 @@ DDB;
public function testDoesNotSupportSavePoints()
{
$this->assertFalse($this->_platform->supportsSavepoints());
$this->assertTrue($this->_platform->supportsSavepoints());
}
public function getGenerateIndexSql()
......
......@@ -125,9 +125,9 @@ class MySqlPlatformTest extends AbstractPlatformTestCase
$this->assertTrue($this->_platform->supportsIdentityColumns());
}
public function testDoesNotSupportSavePoints()
public function testDoesSupportSavePoints()
{
$this->assertFalse($this->_platform->supportsSavepoints());
$this->assertTrue($this->_platform->supportsSavepoints());
}
public function getGenerateIndexSql()
......
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