FIX Add Nested DB transaction support (#7848)

* TEST Prove nested transactions break

* Add nested transaction support
This commit is contained in:
Daniel Hensby 2018-02-08 21:28:32 +00:00 committed by Damian Mooyman
parent 09f88c76cf
commit d3278d5470
2 changed files with 95 additions and 45 deletions

View File

@ -57,6 +57,11 @@ class MySQLDatabase extends Database
*/ */
private static $collation = 'utf8_general_ci'; private static $collation = 'utf8_general_ci';
/**
* @var bool
*/
protected $transactionNesting = 0;
public function connect($parameters) public function connect($parameters)
{ {
// Ensure that driver is available (required by PDO) // Ensure that driver is available (required by PDO)
@ -300,6 +305,9 @@ class MySQLDatabase extends Database
public function transactionStart($transactionMode = false, $sessionCharacteristics = false) public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
{ {
if ($this->transactionNesting > 0) {
$this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting);
} else {
// This sets the isolation level for the NEXT transaction, not the current one. // This sets the isolation level for the NEXT transaction, not the current one.
if ($transactionMode) { if ($transactionMode) {
$this->query('SET TRANSACTION ' . $transactionMode); $this->query('SET TRANSACTION ' . $transactionMode);
@ -311,6 +319,8 @@ class MySQLDatabase extends Database
$this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics); $this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics);
} }
} }
++$this->transactionNesting;
}
public function transactionSavepoint($savepoint) public function transactionSavepoint($savepoint)
{ {
@ -321,15 +331,24 @@ class MySQLDatabase extends Database
{ {
if ($savepoint) { if ($savepoint) {
$this->query('ROLLBACK TO ' . $savepoint); $this->query('ROLLBACK TO ' . $savepoint);
} else {
--$this->transactionNesting;
if ($this->transactionNesting > 0) {
$this->transactionRollback('NESTEDTRANSACTION' . $this->transactionNesting);
} else { } else {
$this->query('ROLLBACK'); $this->query('ROLLBACK');
} }
} }
}
public function transactionEnd($chain = false) public function transactionEnd($chain = false)
{ {
--$this->transactionNesting;
if ($this->transactionNesting <= 0) {
$this->transactionNesting = 0;
$this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN'); $this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN');
} }
}
public function comparisonClause( public function comparisonClause(
$field, $field,

View File

@ -5,18 +5,52 @@ namespace SilverStripe\ORM\Tests;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\Tests\TransactionTest\TestObject;
class TransactionTest extends SapphireTest class TransactionTest extends SapphireTest
{ {
protected $usesDatabase = true;
protected static $extra_dataobjects = array( protected static $extra_dataobjects = [
TransactionTest\TestObject::class TransactionTest\TestObject::class,
); ];
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
if (!DB::get_conn()->supportsTransactions()) {
static::markTestSkipped('Current database does not support transactions');
}
}
public function testNestedTransaction()
{
$this->assertCount(0, TestObject::get());
try {
DB::get_conn()->withTransaction(function () {
$obj = TransactionTest\TestObject::create();
$obj->Title = 'Test';
$obj->write();
$this->assertCount(1, TestObject::get());
DB::get_conn()->withTransaction(function () {
$obj = TransactionTest\TestObject::create();
$obj->Title = 'Test2';
$obj->write();
$this->assertCount(2, TestObject::get());
});
throw new \Exception('roll back transaction');
});
} catch (\Exception $e) {
$this->assertEquals('roll back transaction', $e->getMessage());
}
$this->assertCount(0, TestObject::get());
}
public function testCreateWithTransaction() public function testCreateWithTransaction()
{ {
if (DB::get_conn()->supportsTransactions()==true) {
DB::get_conn()->transactionStart(); DB::get_conn()->transactionStart();
$obj = new TransactionTest\TestObject(); $obj = new TransactionTest\TestObject();
$obj->Title = 'First page'; $obj->Title = 'First page';
@ -54,8 +88,5 @@ class TransactionTest extends SapphireTest
//These pages should NOT exist, we reverted to a savepoint: //These pages should NOT exist, we reverted to a savepoint:
$this->assertFalse(is_object($third) && $third->exists()); $this->assertFalse(is_object($third) && $third->exists());
$this->assertFalse(is_object($fourth) && $fourth->exists()); $this->assertFalse(is_object($fourth) && $fourth->exists());
} else {
$this->markTestSkipped('Current database does not support transactions');
}
} }
} }