View source
<?php
namespace Drupal\system\Tests\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\TransactionOutOfOrderException;
use Drupal\Core\Database\TransactionNoActiveException;
class TransactionTest extends DatabaseTestBase {
protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
$connection = Database::getConnection();
$depth = $connection
->transactionDepth();
$txn = db_transaction();
db_insert('test')
->fields(array(
'name' => 'David' . $suffix,
'age' => '24',
))
->execute();
$this
->assertTrue($connection
->inTransaction(), 'In transaction before calling nested transaction.');
$this
->transactionInnerLayer($suffix, $rollback, $ddl_statement);
$this
->assertTrue($connection
->inTransaction(), 'In transaction after calling nested transaction.');
if ($rollback) {
$txn
->rollback();
$this
->assertTrue($connection
->transactionDepth() == $depth, 'Transaction has rolled back to the last savepoint after calling rollback().');
}
}
protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
$connection = Database::getConnection();
$depth = $connection
->transactionDepth();
$txn = db_transaction();
$depth2 = $connection
->transactionDepth();
$this
->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.');
db_insert('test')
->fields(array(
'name' => 'Daniel' . $suffix,
'age' => '19',
))
->execute();
$this
->assertTrue($connection
->inTransaction(), 'In transaction inside nested transaction.');
if ($ddl_statement) {
$table = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
),
'primary key' => array(
'id',
),
);
db_create_table('database_test_1', $table);
$this
->assertTrue($connection
->inTransaction(), 'In transaction inside nested transaction.');
}
if ($rollback) {
$txn
->rollback();
$this
->assertTrue($connection
->transactionDepth() == $depth, 'Transaction has rolled back to the last savepoint after calling rollback().');
}
}
function testTransactionRollBackSupported() {
if (!Database::getConnection()
->supportsTransactions()) {
return;
}
try {
$this
->transactionOuterLayer('B', TRUE);
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(
':name' => 'DavidB',
))
->fetchField();
$this
->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(
':name' => 'DanielB',
))
->fetchField();
$this
->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
} catch (\Exception $e) {
$this
->fail($e
->getMessage());
}
}
function testTransactionRollBackNotSupported() {
if (Database::getConnection()
->supportsTransactions()) {
return;
}
try {
$this
->transactionOuterLayer('B', TRUE);
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(
':name' => 'DavidB',
))
->fetchField();
$this
->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(
':name' => 'DanielB',
))
->fetchField();
$this
->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
} catch (\Exception $e) {
$this
->fail($e
->getMessage());
}
}
function testCommittedTransaction() {
try {
$this
->transactionOuterLayer('A');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(
':name' => 'DavidA',
))
->fetchField();
$this
->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(
':name' => 'DanielA',
))
->fetchField();
$this
->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
} catch (\Exception $e) {
$this
->fail($e
->getMessage());
}
}
function testTransactionWithDdlStatement() {
$transaction = db_transaction();
$this
->insertRow('row');
$this
->executeDDLStatement();
unset($transaction);
$this
->assertRowPresent('row');
$this
->cleanUp();
$transaction = db_transaction();
$this
->executeDDLStatement();
$this
->insertRow('row');
unset($transaction);
$this
->assertRowPresent('row');
$this
->cleanUp();
$transaction = db_transaction();
$transaction2 = db_transaction();
$this
->executeDDLStatement();
unset($transaction2);
$transaction3 = db_transaction();
$this
->insertRow('row');
unset($transaction3);
unset($transaction);
$this
->assertRowPresent('row');
$this
->cleanUp();
$transaction = db_transaction();
$transaction2 = db_transaction();
$this
->executeDDLStatement();
unset($transaction2);
$transaction3 = db_transaction();
$this
->insertRow('row');
$transaction3
->rollback();
unset($transaction3);
unset($transaction);
$this
->assertRowAbsent('row');
if (Database::getConnection()
->supportsTransactionalDDL()) {
$this
->cleanUp();
$transaction = db_transaction();
$this
->insertRow('row');
$this
->executeDDLStatement();
$transaction
->rollback();
unset($transaction);
$this
->assertRowAbsent('row');
$this
->cleanUp();
$transaction = db_transaction();
$transaction2 = db_transaction();
$this
->executeDDLStatement();
unset($transaction2);
$transaction3 = db_transaction();
$this
->insertRow('row');
unset($transaction3);
$transaction
->rollback();
unset($transaction);
$this
->assertRowAbsent('row');
}
else {
$this
->cleanUp();
$transaction = db_transaction();
$this
->insertRow('row');
$this
->executeDDLStatement();
try {
$transaction
->rollback();
unset($transaction);
} catch (TransactionNoActiveException $e) {
$this
->pass('Rolling back a transaction containing DDL should fail.');
}
$this
->assertRowPresent('row');
}
}
protected function insertRow($name) {
db_insert('test')
->fields(array(
'name' => $name,
))
->execute();
}
protected function executeDDLStatement() {
static $count = 0;
$table = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
),
'primary key' => array(
'id',
),
);
db_create_table('database_test_' . ++$count, $table);
}
protected function cleanUp() {
db_truncate('test')
->execute();
}
function assertRowPresent($name, $message = NULL) {
if (!isset($message)) {
$message = format_string('Row %name is present.', array(
'%name' => $name,
));
}
$present = (bool) db_query('SELECT 1 FROM {test} WHERE name = :name', array(
':name' => $name,
))
->fetchField();
return $this
->assertTrue($present, $message);
}
function assertRowAbsent($name, $message = NULL) {
if (!isset($message)) {
$message = format_string('Row %name is absent.', array(
'%name' => $name,
));
}
$present = (bool) db_query('SELECT 1 FROM {test} WHERE name = :name', array(
':name' => $name,
))
->fetchField();
return $this
->assertFalse($present, $message);
}
function testTransactionStacking() {
if (!Database::getConnection()
->supportsTransactions()) {
return;
}
$database = Database::getConnection();
$transaction = db_transaction();
$this
->insertRow('outer');
$transaction2 = db_transaction();
$this
->insertRow('inner');
unset($transaction2);
$this
->assertTrue($database
->inTransaction(), 'Still in a transaction after popping the inner transaction');
unset($transaction);
$this
->assertFalse($database
->inTransaction(), 'Transaction closed after popping the outer transaction');
$this
->assertRowPresent('outer');
$this
->assertRowPresent('inner');
$this
->cleanUp();
$transaction = db_transaction();
$this
->insertRow('outer');
$transaction2 = db_transaction();
$this
->insertRow('inner');
unset($transaction);
$this
->insertRow('inner-after-outer-commit');
$this
->assertTrue($database
->inTransaction(), 'Still in a transaction after popping the outer transaction');
unset($transaction2);
$this
->assertFalse($database
->inTransaction(), 'Transaction closed after popping the inner transaction');
$this
->assertRowPresent('outer');
$this
->assertRowPresent('inner');
$this
->assertRowPresent('inner-after-outer-commit');
$this
->cleanUp();
$transaction = db_transaction();
$this
->insertRow('outer');
$transaction2 = db_transaction();
$this
->insertRow('inner');
$transaction2
->rollback();
unset($transaction2);
$this
->assertTrue($database
->inTransaction(), 'Still in a transaction after popping the outer transaction');
$this
->insertRow('outer-after-inner-rollback');
unset($transaction);
$this
->assertFalse($database
->inTransaction(), 'Transaction closed after popping the inner transaction');
$this
->assertRowPresent('outer');
$this
->assertRowAbsent('inner');
$this
->assertRowPresent('outer-after-inner-rollback');
$this
->cleanUp();
$transaction = db_transaction();
$this
->insertRow('outer');
$transaction2 = db_transaction();
$this
->insertRow('inner');
unset($transaction);
$this
->assertTrue($database
->inTransaction(), 'Still in a transaction after popping the outer transaction');
$transaction2
->rollback();
unset($transaction2);
$this
->assertFalse($database
->inTransaction(), 'Transaction closed after popping the inner transaction');
$this
->assertRowPresent('outer');
$this
->assertRowAbsent('inner');
$this
->cleanUp();
$transaction = db_transaction();
$this
->insertRow('outer');
$transaction2 = db_transaction();
$this
->insertRow('inner');
$transaction3 = db_transaction();
$this
->insertRow('inner2');
try {
$transaction
->rollback();
unset($transaction);
$this
->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
} catch (TransactionOutOfOrderException $e) {
$this
->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
}
$this
->assertFalse($database
->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
unset($transaction3);
$this
->pass('Trying to commit an inner transaction resulted in an exception.');
try {
$transaction
->rollback();
unset($transaction2);
$this
->fail('Trying to commit an inner transaction resulted in an exception.');
} catch (TransactionNoActiveException $e) {
$this
->pass('Trying to commit an inner transaction resulted in an exception.');
}
$this
->assertRowAbsent('outer');
$this
->assertRowAbsent('inner');
$this
->assertRowAbsent('inner2');
}
}