Index: includes/database/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/database.inc,v retrieving revision 1.147 diff -u -p -r1.147 database.inc --- includes/database/database.inc 22 Dec 2010 07:48:56 -0000 1.147 +++ includes/database/database.inc 13 Jan 2011 22:06:41 -0000 @@ -195,7 +195,7 @@ abstract class DatabaseConnection extend /** * The key representing this connection. - * + * * The key is a unique string which identifies a database connection. A * connection can be a single server or a cluster of master and slaves (use * target to pick between master and slave). @@ -1030,7 +1030,22 @@ abstract class DatabaseConnection extend } } else { - $this->query('RELEASE SAVEPOINT ' . $name); + // In database systems which do not support transactional DDL, previous + // queries may have implicitly committed pending transactions. To avoid + // exceptions when no actual error has occurred, we silently succeed + // for PDOExceptions with error code 42000 ("Syntax error or access rule + // violation"). + try { + $this->query('RELEASE SAVEPOINT ' . $name); + } + catch (PDOException $e) { + 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; } } Index: modules/simpletest/tests/database_test.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v retrieving revision 1.111 diff -u -p -r1.111 database_test.test --- modules/simpletest/tests/database_test.test 10 Jan 2011 18:04:23 -0000 1.111 +++ modules/simpletest/tests/database_test.test 13 Jan 2011 22:06:43 -0000 @@ -3216,8 +3216,10 @@ class DatabaseTransactionTestCase extend * Suffix to add to field values to differentiate tests. * @param $rollback * 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(); $depth = $connection->transactionDepth(); $txn = db_transaction(); @@ -3234,7 +3236,7 @@ class DatabaseTransactionTestCase extend // We're already in a transaction, but we call ->transactionInnerLayer // 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.')); @@ -3254,12 +3256,12 @@ class DatabaseTransactionTestCase extend * Suffix to add to field values to differentiate tests. * @param $rollback * 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(); - $this->assertTrue($connection->inTransaction(), t('In transaction in nested transaction.')); - $depth = $connection->transactionDepth(); // Start a transaction. If we're being called from ->transactionOuterLayer, // then we're already in a transaction. Normally, that would make starting @@ -3280,6 +3282,22 @@ class DatabaseTransactionTestCase extend $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) { // Roll back the transaction, if requested. // This rollback should propagate to the last savepoint. @@ -3361,6 +3379,43 @@ class DatabaseTransactionTestCase extend $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.')); + } + } }