Use PHP 7 '??' operator instead of '?:' with 'isset()' where convenient
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / rdbms / database / DatabaseSQLTest.php
index 3756da2..d9919e1 100644 (file)
@@ -46,15 +46,17 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
         * @covers Wikimedia\Rdbms\Database::makeSelectOptions
         * @covers Wikimedia\Rdbms\Database::makeOrderBy
         * @covers Wikimedia\Rdbms\Database::makeGroupByWithHaving
+        * @covers Wikimedia\Rdbms\Database::selectFieldsOrOptionsAggregate
+        * @covers Wikimedia\Rdbms\Database::selectOptionsIncludeLocking
         */
        public function testSelect( $sql, $sqlText ) {
                $this->database->select(
                        $sql['tables'],
                        $sql['fields'],
-                       isset( $sql['conds'] ) ? $sql['conds'] : [],
+                       $sql['conds'] ?? [],
                        __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : [],
-                       isset( $sql['join_conds'] ) ? $sql['join_conds'] : []
+                       $sql['options'] ?? [],
+                       $sql['join_conds'] ?? []
                );
                $this->assertLastSql( $sqlText );
        }
@@ -223,9 +225,17 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                                [
                                        'tables' => 'table',
                                        'fields' => [ 'field' ],
-                                       'options' => [ 'DISTINCT', 'LOCK IN SHARE MODE' ],
+                                       'options' => [ 'DISTINCT' ],
                                ],
-                               "SELECT DISTINCT field FROM table      LOCK IN SHARE MODE"
+                               "SELECT DISTINCT field FROM table"
+                       ],
+                       [
+                               [
+                                       'tables' => 'table',
+                                       'fields' => [ 'field' ],
+                                       'options' => [ 'LOCK IN SHARE MODE' ],
+                               ],
+                               "SELECT field FROM table      LOCK IN SHARE MODE"
                        ],
                        [
                                [
@@ -256,10 +266,10 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->database->selectRowCount(
                        $sql['tables'],
                        $sql['field'],
-                       isset( $sql['conds'] ) ? $sql['conds'] : [],
+                       $sql['conds'] ?? [],
                        __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : [],
-                       isset( $sql['join_conds'] ) ? $sql['join_conds'] : []
+                       $sql['options'] ?? [],
+                       $sql['join_conds'] ?? []
                );
                $this->assertLastSql( $sqlText );
        }
@@ -353,7 +363,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $sql['values'],
                        $sql['conds'],
                        __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : []
+                       $sql['options'] ?? []
                );
                $this->assertLastSql( $sqlText );
        }
@@ -521,7 +531,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $sql['table'],
                        $sql['rows'],
                        __METHOD__,
-                       isset( $sql['options'] ) ? $sql['options'] : []
+                       $sql['options'] ?? []
                );
                $this->assertLastSql( $sqlText );
        }
@@ -577,9 +587,9 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $sql['varMap'],
                        $sql['conds'],
                        __METHOD__,
-                       isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
-                       isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
-                       isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
+                       $sql['insertOptions'] ?? [],
+                       $sql['selectOptions'] ?? [],
+                       $sql['selectJoinConds'] ?? []
                );
                $this->assertLastSql( $sqlTextNative );
 
@@ -593,9 +603,9 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $sql['varMap'],
                        $sql['conds'],
                        __METHOD__,
-                       isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [],
-                       isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [],
-                       isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : []
+                       $sql['insertOptions'] ?? [],
+                       $sql['selectOptions'] ?? [],
+                       $sql['selectJoinConds'] ?? []
                );
                $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, 'BEGIN', $sqlInsert, 'COMMIT' ] ), $dbWeb );
        }
@@ -1002,10 +1012,10 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $params['table'],
                        $params['vars'],
                        $params['permute_conds'],
-                       isset( $params['extra_conds'] ) ? $params['extra_conds'] : '',
+                       $params['extra_conds'] ?? '',
                        'FNAME',
-                       isset( $params['options'] ) ? $params['options'] : [],
-                       isset( $params['join_conds'] ) ? $params['join_conds'] : []
+                       $params['options'] ?? [],
+                       $params['join_conds'] ?? []
                ) );
                $this->assertEquals( $expect, $sql );
        }
@@ -1418,6 +1428,143 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                // phpcs:ignore Generic.Files.LineLength
                $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK' );
 
+               $fname = __METHOD__;
+               $triggerMap = [
+                       '-' => '-',
+                       IDatabase::TRIGGER_COMMIT => 'tCommit',
+                       IDatabase::TRIGGER_ROLLBACK => 'tRollback'
+               ];
+               $pcCallback = function ( IDatabase $db ) use ( $fname ) {
+                       $this->database->query( "SELECT 0", $fname );
+               };
+               $callback1 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
+                       $this->database->query( "SELECT 1, {$triggerMap[$trigger]} AS t", $fname );
+               };
+               $callback2 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
+                       $this->database->query( "SELECT 2, {$triggerMap[$trigger]} AS t", $fname );
+               };
+               $callback3 = function ( $trigger = '-' ) use ( $fname, $triggerMap ) {
+                       $this->database->query( "SELECT 3, {$triggerMap[$trigger]} AS t", $fname );
+               };
+
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->assertLastSql( 'BEGIN; ROLLBACK' );
+
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->assertLastSql( 'BEGIN; ROLLBACK' );
+
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionResolution( $callback1, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->assertLastSql( 'BEGIN; ROLLBACK; SELECT 1, tRollback AS t' );
+
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'SELECT 0',
+                       'SELECT 0',
+                       'COMMIT'
+               ] ) );
+
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->onTransactionCommitOrIdle( $callback3, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'COMMIT',
+                       'SELECT 1, tCommit AS t',
+                       'SELECT 3, tCommit AS t'
+               ] ) );
+
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->onTransactionResolution( $callback1, __METHOD__ );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionResolution( $callback2, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'COMMIT',
+                       'SELECT 1, tCommit AS t',
+                       'SELECT 2, tRollback AS t',
+                       'SELECT 3, tCommit AS t'
+               ] ) );
+
+               $makeCallback = function ( $id ) use ( $fname, $triggerMap ) {
+                       return function ( $trigger = '-' ) use ( $id, $fname, $triggerMap ) {
+                               $this->database->query( "SELECT $id, {$triggerMap[$trigger]} AS t", $fname );
+                       };
+               };
+
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'COMMIT',
+                       'SELECT 1, tRollback AS t'
+               ] ) );
+
+               $this->database->startAtomic( __METHOD__ . '_level1', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionResolution( $makeCallback( 1 ), __METHOD__ );
+               $this->database->startAtomic( __METHOD__ . '_level2' );
+               $this->database->startAtomic( __METHOD__ . '_level3', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionResolution( $makeCallback( 2 ), __METHOD__ );
+               $this->database->endAtomic( __METHOD__ );
+               $this->database->onTransactionResolution( $makeCallback( 3 ), __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ . '_level3' );
+               $this->database->endAtomic( __METHOD__ . '_level2' );
+               $this->database->onTransactionResolution( $makeCallback( 4 ), __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_level1' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'SAVEPOINT wikimedia_rdbms_atomic2',
+                       'RELEASE SAVEPOINT wikimedia_rdbms_atomic2',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'COMMIT; SELECT 1, tCommit AS t',
+                       'SELECT 2, tRollback AS t',
+                       'SELECT 3, tRollback AS t',
+                       'SELECT 4, tCommit AS t'
+               ] ) );
+       }
+
+       /**
+        * @covers \Wikimedia\Rdbms\Database::doSavepoint
+        * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+        * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+        * @covers \Wikimedia\Rdbms\Database::startAtomic
+        * @covers \Wikimedia\Rdbms\Database::endAtomic
+        * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+        * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+        */
+       public function testAtomicSectionsRecovery() {
                $this->database->begin( __METHOD__ );
                try {
                        $this->database->doAtomicSection(
@@ -1450,10 +1597,169 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                } catch ( RuntimeException $ex ) {
                        $this->assertSame( 'Test exception', $ex->getMessage() );
                }
+               try {
+                       $this->database->commit( __METHOD__ );
+                       $this->fail( 'Test exception not thrown' );
+               } catch ( DBTransactionError $ex ) {
+                       $this->assertSame(
+                               'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+                               $ex->getMessage()
+                       );
+               }
                $this->database->rollback( __METHOD__ );
                $this->assertLastSql( 'BEGIN; ROLLBACK' );
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\Database::doSavepoint
+        * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+        * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+        * @covers \Wikimedia\Rdbms\Database::startAtomic
+        * @covers \Wikimedia\Rdbms\Database::endAtomic
+        * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+        * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+        */
+       public function testAtomicSectionsCallbackCancellation() {
+               $fname = __METHOD__;
+               $callback1Called = null;
+               $callback1 = function ( $trigger = '-' ) use ( $fname, &$callback1Called ) {
+                       $callback1Called = $trigger;
+                       $this->database->query( "SELECT 1", $fname );
+               };
+               $callback2Called = null;
+               $callback2 = function ( $trigger = '-' ) use ( $fname, &$callback2Called ) {
+                       $callback2Called = $trigger;
+                       $this->database->query( "SELECT 2", $fname );
+               };
+               $callback3Called = null;
+               $callback3 = function ( $trigger = '-' ) use ( $fname, &$callback3Called ) {
+                       $callback3Called = $trigger;
+                       $this->database->query( "SELECT 3", $fname );
+               };
+
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__ . '_inner' );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_inner' );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertNull( $callback1Called );
+               $this->assertNull( $callback2Called );
+               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               // phpcs:ignore Generic.Files.LineLength
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
+
+               $callback1Called = null;
+               $callback2Called = null;
+               $callback3Called = null;
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_inner' );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertNull( $callback1Called );
+               $this->assertNull( $callback2Called );
+               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               // phpcs:ignore Generic.Files.LineLength
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
+
+               $callback1Called = null;
+               $callback2Called = null;
+               $callback3Called = null;
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__ . '_inner' );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__, $atomicId );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertNull( $callback1Called );
+               $this->assertNull( $callback2Called );
+               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+
+               $callback1Called = null;
+               $callback2Called = null;
+               $callback3Called = null;
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__ . '_inner' );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               try {
+                       $this->database->cancelAtomic( __METHOD__ . '_X', $atomicId );
+               } catch ( DBUnexpectedError $e ) {
+                       $m = __METHOD__;
+                       $this->assertSame(
+                               "Invalid atomic section ended (got {$m}_X but expected {$m}).",
+                               $e->getMessage()
+                       );
+               }
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertNull( $callback1Called );
+               $this->assertNull( $callback2Called );
+               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__ . '_inner' );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ . '_inner' );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertNull( $callback1Called );
+               $this->assertNull( $callback2Called );
+               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+
+               $wrapper = TestingAccessWrapper::newFromObject( $this->database );
+               $callback1Called = null;
+               $callback2Called = null;
+               $callback3Called = null;
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__ . '_inner' );
+               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
+               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
+               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
+               $this->database->cancelAtomic( __METHOD__ . '_inner' );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertNull( $callback1Called );
+               $this->assertNull( $callback2Called );
+               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+       }
+
+       /**
+        * @covers \Wikimedia\Rdbms\Database::doSavepoint
+        * @covers \Wikimedia\Rdbms\Database::doReleaseSavepoint
+        * @covers \Wikimedia\Rdbms\Database::doRollbackToSavepoint
+        * @covers \Wikimedia\Rdbms\Database::startAtomic
+        * @covers \Wikimedia\Rdbms\Database::endAtomic
+        * @covers \Wikimedia\Rdbms\Database::cancelAtomic
+        * @covers \Wikimedia\Rdbms\Database::doAtomicSection
+        */
+       public function testAtomicSectionsTrxRound() {
+               $this->database->setFlag( IDatabase::DBO_TRX );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->query( 'SELECT 1', __METHOD__ );
+               $this->database->endAtomic( __METHOD__ );
+               $this->database->commit( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               // phpcs:ignore Generic.Files.LineLength
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SELECT 1; RELEASE SAVEPOINT wikimedia_rdbms_atomic1; COMMIT' );
+       }
+
        public static function provideAtomicSectionMethodsForErrors() {
                return [
                        [ 'endAtomic' ],
@@ -1587,15 +1893,31 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
 
                $this->database->setFlag( Database::DBO_TRX );
 
-               // Implicit transaction gets silently rolled back
+               // Implicit transaction does not get silently rolled back
                $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
                call_user_func( $doError );
-               $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
-               $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
-               // phpcs:ignore
-               $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+               try {
+                       $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBTransactionError $e ) {
+                       $this->assertEquals(
+                               'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+                               $e->getMessage()
+                       );
+               }
+               try {
+                       $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBTransactionError $e ) {
+                       $this->assertEquals(
+                               'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+                               $e->getMessage()
+                       );
+               }
+               $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL );
+               $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK' );
 
-               // ... unless there were prior writes
+               // Likewise if there were prior writes
                $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
                $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
                call_user_func( $doError );
@@ -1680,7 +2002,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
        public function testPrematureClose1() {
                $fname = __METHOD__;
                $this->database->begin( __METHOD__ );
-               $this->database->onTransactionIdle( function () use ( $fname ) {
+               $this->database->onTransactionCommitOrIdle( function () use ( $fname ) {
                        $this->database->query( 'SELECT 1', $fname );
                } );
                $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
@@ -1698,7 +2020,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                try {
                        $fname = __METHOD__;
                        $this->database->startAtomic( __METHOD__ );
-                       $this->database->onTransactionIdle( function () use ( $fname ) {
+                       $this->database->onTransactionCommitOrIdle( function () use ( $fname ) {
                                $this->database->query( 'SELECT 1', $fname );
                        } );
                        $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );