Issue #1007830 by drunken monkey, Damien Tournoud, bfroehle: Fixed Nested transactions throw exceptions on ddl changes.
parent
86bf589b56
commit
33d4ce4bde
|
@ -133,6 +133,55 @@ class DatabaseConnection_mysql extends DatabaseConnection {
|
||||||
catch (PDOException $e) {
|
catch (PDOException $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to work around issues to MySQL not supporting transactional DDL.
|
||||||
|
*/
|
||||||
|
public function popTransaction($name) {
|
||||||
|
if (!$this->supportsTransactions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$this->inTransaction()) {
|
||||||
|
throw new DatabaseTransactionNoActiveException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit everything since SAVEPOINT $name.
|
||||||
|
while ($savepoint = array_pop($this->transactionLayers)) {
|
||||||
|
if ($savepoint != $name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no more layers left then we should commit.
|
||||||
|
if (empty($this->transactionLayers)) {
|
||||||
|
if (!PDO::commit()) {
|
||||||
|
throw new DatabaseTransactionCommitFailedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Attempt to release this savepoint in the standard way.
|
||||||
|
try {
|
||||||
|
$this->query('RELEASE SAVEPOINT ' . $name);
|
||||||
|
}
|
||||||
|
catch (PDOException $e) {
|
||||||
|
// However, in MySQL (InnoDB), savepoints are automatically committed
|
||||||
|
// when tables are altered or created (DDL transactions are not
|
||||||
|
// supported). This can cause exceptions due to trying to release
|
||||||
|
// savepoints which no longer exist.
|
||||||
|
//
|
||||||
|
// To avoid exceptions when no actual error has occurred, we silently
|
||||||
|
// succeed for PDOExceptions with error code 42000 ("Syntax error or
|
||||||
|
// access rule violation").
|
||||||
|
if ($e->getCode() != '42000') {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
// If one SAVEPOINT was released automatically, then all were.
|
||||||
|
// Therefore, we keep just the topmost transaction.
|
||||||
|
$this->transactionLayers = array('drupal_transaction');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3251,8 +3251,10 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
|
||||||
* Suffix to add to field values to differentiate tests.
|
* Suffix to add to field values to differentiate tests.
|
||||||
* @param $rollback
|
* @param $rollback
|
||||||
* Whether or not to try rolling back the transaction when we're done.
|
* Whether or not to try rolling back the transaction when we're done.
|
||||||
|
* @param $ddl_statement
|
||||||
|
* Whether to execute a DDL statement during the inner transaction.
|
||||||
*/
|
*/
|
||||||
protected function transactionOuterLayer($suffix, $rollback = FALSE) {
|
protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
|
||||||
$connection = Database::getConnection();
|
$connection = Database::getConnection();
|
||||||
$depth = $connection->transactionDepth();
|
$depth = $connection->transactionDepth();
|
||||||
$txn = db_transaction();
|
$txn = db_transaction();
|
||||||
|
@ -3269,7 +3271,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
|
||||||
|
|
||||||
// We're already in a transaction, but we call ->transactionInnerLayer
|
// We're already in a transaction, but we call ->transactionInnerLayer
|
||||||
// to nest another transaction inside the current one.
|
// to nest another transaction inside the current one.
|
||||||
$this->transactionInnerLayer($suffix, $rollback);
|
$this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
|
||||||
|
|
||||||
$this->assertTrue($connection->inTransaction(), t('In transaction after calling nested transaction.'));
|
$this->assertTrue($connection->inTransaction(), t('In transaction after calling nested transaction.'));
|
||||||
|
|
||||||
|
@ -3289,12 +3291,12 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
|
||||||
* Suffix to add to field values to differentiate tests.
|
* Suffix to add to field values to differentiate tests.
|
||||||
* @param $rollback
|
* @param $rollback
|
||||||
* Whether or not to try rolling back the transaction when we're done.
|
* Whether or not to try rolling back the transaction when we're done.
|
||||||
|
* @param $ddl_statement
|
||||||
|
* Whether to execute a DDL statement during the transaction.
|
||||||
*/
|
*/
|
||||||
protected function transactionInnerLayer($suffix, $rollback = FALSE) {
|
protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
|
||||||
$connection = Database::getConnection();
|
$connection = Database::getConnection();
|
||||||
|
|
||||||
$this->assertTrue($connection->inTransaction(), t('In transaction in nested transaction.'));
|
|
||||||
|
|
||||||
$depth = $connection->transactionDepth();
|
$depth = $connection->transactionDepth();
|
||||||
// Start a transaction. If we're being called from ->transactionOuterLayer,
|
// Start a transaction. If we're being called from ->transactionOuterLayer,
|
||||||
// then we're already in a transaction. Normally, that would make starting
|
// then we're already in a transaction. Normally, that would make starting
|
||||||
|
@ -3315,6 +3317,22 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
|
||||||
|
|
||||||
$this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.'));
|
$this->assertTrue($connection->inTransaction(), t('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(), t('In transaction inside nested transaction.'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($rollback) {
|
if ($rollback) {
|
||||||
// Roll back the transaction, if requested.
|
// Roll back the transaction, if requested.
|
||||||
// This rollback should propagate to the last savepoint.
|
// This rollback should propagate to the last savepoint.
|
||||||
|
@ -3396,6 +3414,43 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
|
||||||
$this->fail($e->getMessage());
|
$this->fail($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the compatibility of transactions with DDL statements.
|
||||||
|
*/
|
||||||
|
function testTransactionWithDdlStatement() {
|
||||||
|
// First, test that a commit works normally, even with DDL statements.
|
||||||
|
try {
|
||||||
|
$this->transactionOuterLayer('D', FALSE, TRUE);
|
||||||
|
|
||||||
|
// Because we committed, the inserted rows should both be present.
|
||||||
|
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidD'))->fetchField();
|
||||||
|
$this->assertIdentical($saved_age, '24', t('Can retrieve DavidD row after commit.'));
|
||||||
|
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielD'))->fetchField();
|
||||||
|
$this->assertIdentical($saved_age, '19', t('Can retrieve DanielD row after commit.'));
|
||||||
|
// The created table should also exist.
|
||||||
|
$count = db_query('SELECT COUNT(id) FROM {database_test_1}')->fetchField();
|
||||||
|
$this->assertIdentical($count, '0', t('Table was successfully created inside a transaction.'));
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
$this->fail($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we rollback the transaction, an exception might be thrown.
|
||||||
|
try {
|
||||||
|
$this->transactionOuterLayer('E', TRUE, TRUE);
|
||||||
|
|
||||||
|
// Because we rolled back, the inserted rows shouldn't be present.
|
||||||
|
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidE'))->fetchField();
|
||||||
|
$this->assertNotIdentical($saved_age, '24', t('Cannot retrieve DavidE row after rollback.'));
|
||||||
|
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielE'))->fetchField();
|
||||||
|
$this->assertNotIdentical($saved_age, '19', t('Cannot retrieve DanielE row after rollback.'));
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
// An exception also lets the test pass.
|
||||||
|
$this->assertTrue(true, t('Exception thrown on rollback after a DDL statement was executed.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue