Skip to content

Commit 598b2a3

Browse files
authored
Merge pull request #6812 from lcobucci/convert-exceptions-on-begin-transaction
Convert driver exceptions when starting transactions
2 parents 369ab24 + b6eb520 commit 598b2a3

File tree

4 files changed

+52
-13
lines changed

4 files changed

+52
-13
lines changed

src/Connection.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,11 @@ public function beginTransaction(): void
10271027
++$this->transactionNestingLevel;
10281028

10291029
if ($this->transactionNestingLevel === 1) {
1030-
$connection->beginTransaction();
1030+
try {
1031+
$connection->beginTransaction();
1032+
} catch (Driver\Exception $e) {
1033+
throw $this->convertException($e);
1034+
}
10311035
} else {
10321036
$this->createSavepoint($this->_getNestedTransactionSavePointName());
10331037
}

src/Driver/API/PostgreSQL/ExceptionConverter.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
88
use Doctrine\DBAL\Driver\Exception;
99
use Doctrine\DBAL\Exception\ConnectionException;
10+
use Doctrine\DBAL\Exception\ConnectionLost;
1011
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
1112
use Doctrine\DBAL\Exception\DeadlockException;
1213
use Doctrine\DBAL\Exception\DriverException;
@@ -77,6 +78,10 @@ public function convert(Exception $exception, ?Query $query): DriverException
7778
return new ConnectionException($exception, $query);
7879
}
7980

81+
if (str_contains($exception->getMessage(), 'terminating connection')) {
82+
return new ConnectionLost($exception, $query);
83+
}
84+
8085
return new DriverException($exception, $query);
8186
}
8287
}

src/Driver/Mysqli/Connection.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ public function lastInsertId(): int|string
8080

8181
public function beginTransaction(): void
8282
{
83-
$this->connection->begin_transaction();
83+
if (! $this->connection->begin_transaction()) {
84+
throw ConnectionError::new($this->connection);
85+
}
8486
}
8587

8688
public function commit(): void

tests/Functional/TransactionTest.php

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,48 @@
77
use Doctrine\DBAL\Connection;
88
use Doctrine\DBAL\Exception\ConnectionLost;
99
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
10+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
1011
use Doctrine\DBAL\Schema\Table;
1112
use Doctrine\DBAL\Tests\FunctionalTestCase;
13+
use Doctrine\DBAL\Tests\TestUtil;
1214
use Doctrine\DBAL\Types\Types;
1315

1416
use function func_get_args;
1517
use function restore_error_handler;
1618
use function set_error_handler;
17-
use function sleep;
1819

1920
use const E_WARNING;
2021

2122
class TransactionTest extends FunctionalTestCase
2223
{
24+
public function testBeginTransactionFailure(): void
25+
{
26+
$this->expectConnectionLoss(static function (Connection $connection): void {
27+
$connection->beginTransaction();
28+
});
29+
}
30+
2331
public function testCommitFailure(): void
2432
{
33+
$this->connection->beginTransaction();
34+
2535
$this->expectConnectionLoss(static function (Connection $connection): void {
2636
$connection->commit();
2737
});
2838
}
2939

3040
public function testRollbackFailure(): void
3141
{
42+
$this->connection->beginTransaction();
43+
3244
$this->expectConnectionLoss(static function (Connection $connection): void {
3345
$connection->rollBack();
3446
});
3547
}
3648

3749
private function expectConnectionLoss(callable $scenario): void
3850
{
39-
if (! $this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
40-
self::markTestSkipped('Restricted to MySQL.');
41-
}
42-
43-
$this->connection->executeStatement('SET SESSION wait_timeout=1');
44-
$this->connection->beginTransaction();
45-
46-
// during the sleep MySQL will close the connection
47-
sleep(2);
48-
51+
$this->killCurrentSession();
4952
$this->expectException(ConnectionLost::class);
5053

5154
// prevent the PHPUnit error handler from handling the "MySQL server has gone away" warning
@@ -65,6 +68,31 @@ private function expectConnectionLoss(callable $scenario): void
6568
}
6669
}
6770

71+
private function killCurrentSession(): void
72+
{
73+
$this->markConnectionNotReusable();
74+
75+
$databasePlatform = $this->connection->getDatabasePlatform();
76+
77+
[$currentProcessQuery, $killProcessStatement] = match (true) {
78+
$databasePlatform instanceof AbstractMySqlPlatform => [
79+
'SELECT CONNECTION_ID()',
80+
'KILL ?',
81+
],
82+
$databasePlatform instanceof PostgreSQLPlatform => [
83+
'SELECT pg_backend_pid()',
84+
'SELECT pg_terminate_backend(?)',
85+
],
86+
default => self::markTestSkipped('Unsupported test platform.'),
87+
};
88+
89+
$privilegedConnection = TestUtil::getPrivilegedConnection();
90+
$privilegedConnection->executeStatement(
91+
$killProcessStatement,
92+
[$this->connection->executeQuery($currentProcessQuery)->fetchOne()],
93+
);
94+
}
95+
6896
public function testNestedTransactionWalkthrough(): void
6997
{
7098
if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {

0 commit comments

Comments
 (0)