Skip to content

Commit 0ec9dea

Browse files
committed
Fix incorrect handling of transactions using deferred constraints
1 parent c204fe1 commit 0ec9dea

File tree

4 files changed

+327
-16
lines changed

4 files changed

+327
-16
lines changed

src/Connection.php

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,16 +1283,39 @@ public function transactional(Closure $func)
12831283

12841284
try {
12851285
$res = $func($this);
1286-
$this->commit();
12871286

12881287
$successful = true;
1289-
1290-
return $res;
12911288
} finally {
12921289
if (! $successful) {
12931290
$this->rollBack();
12941291
}
12951292
}
1293+
1294+
$this->commit();
1295+
// $successful = false;
1296+
// try {
1297+
// $this->commit();
1298+
//
1299+
// $successful = true;
1300+
// } finally {
1301+
// if (! $successful) {
1302+
// $this->rollBack();
1303+
// }
1304+
// }
1305+
1306+
return $res;
1307+
1308+
// try {
1309+
// $res = $func($this);
1310+
// } catch (Throwable $e) {
1311+
// $this->rollBack();
1312+
//
1313+
// throw $e;
1314+
// }
1315+
//
1316+
// $this->commit();
1317+
//
1318+
// return $res;
12961319
}
12971320

12981321
/**
@@ -1424,12 +1447,21 @@ public function commit()
14241447

14251448
$connection = $this->getWrappedConnection();
14261449

1427-
if ($this->transactionNestingLevel === 1) {
1428-
$result = $this->doCommit($connection);
1429-
} elseif ($this->nestTransactionsWithSavepoints) {
1430-
$this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1450+
try {
1451+
if ($this->transactionNestingLevel === 1) {
1452+
$result = $this->doCommit($connection);
1453+
} elseif ($this->nestTransactionsWithSavepoints) {
1454+
$this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1455+
}
1456+
} finally {
1457+
$this->updateTransactionStateAfterCommit();
14311458
}
14321459

1460+
return $result;
1461+
}
1462+
1463+
private function updateTransactionStateAfterCommit(): void
1464+
{
14331465
--$this->transactionNestingLevel;
14341466

14351467
$eventManager = $this->getEventManager();
@@ -1446,12 +1478,10 @@ public function commit()
14461478
}
14471479

14481480
if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1449-
return $result;
1481+
return;
14501482
}
14511483

14521484
$this->beginTransaction();
1453-
1454-
return $result;
14551485
}
14561486

14571487
/**
@@ -1467,7 +1497,11 @@ private function doCommit(DriverConnection $connection)
14671497
$logger->startQuery('"COMMIT"');
14681498
}
14691499

1470-
$result = $connection->commit();
1500+
try {
1501+
$result = $connection->commit();
1502+
} catch (Driver\Exception $e) {
1503+
throw $this->convertExceptionDuringQuery($e, 'COMMIT');
1504+
}
14711505

14721506
if ($logger !== null) {
14731507
$logger->stopQuery();

src/Driver/PDO/PDOException.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
use Doctrine\DBAL\Driver\Exception as DriverException;
88

9+
use function explode;
10+
use function is_numeric;
11+
use function str_replace;
12+
use function strpos;
13+
914
/**
1015
* @internal
1116
*
@@ -17,10 +22,26 @@ final class PDOException extends \PDOException implements DriverException
1722

1823
public static function new(\PDOException $previous): self
1924
{
20-
$exception = new self($previous->message, 0, $previous);
25+
if (isset($previous->errorInfo[2]) && strpos($previous->errorInfo[2], 'OCITransCommit: ORA-02091') === 0) {
26+
// With pdo_oci driver, the root-cause error is in the second line
27+
//ORA-02091: transaction rolled back
28+
//ORA-00001: unique constraint (DOCTRINE.GH3423_UNIQUE) violated
29+
[$firstMessage, $secondMessage] = explode("\n", $previous->message, 2);
30+
31+
[$code, $message] = explode(': ', $secondMessage, 2);
32+
$code = (int) str_replace('ORA-', '', $code);
33+
} else {
34+
$message = $previous->message;
35+
if (is_numeric($previous->code)) {
36+
$code = (int) $previous->code;
37+
} else {
38+
$code = $previous->errorInfo[1] ?? 0;
39+
}
40+
}
41+
42+
$exception = new self($message, $code, $previous);
2143

2244
$exception->errorInfo = $previous->errorInfo;
23-
$exception->code = $previous->code;
2445
$exception->sqlState = $previous->errorInfo[0] ?? null;
2546

2647
return $exception;

tests/Functional/TransactionTest.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Doctrine\DBAL\Driver\Exception as DriverException;
66
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
77
use Doctrine\DBAL\Tests\FunctionalTestCase;
8-
use PDOException;
98

109
use function sleep;
1110

@@ -29,8 +28,8 @@ public function testCommitFalse(): void
2928
sleep(2); // during the sleep mysql will close the connection
3029

3130
try {
32-
self::assertFalse(@$this->connection->commit()); // we will ignore `MySQL server has gone away` warnings
33-
} catch (PDOException $e) {
31+
self::assertFalse(@$this->connection->commit()); // we will ignore `Packets out of order.` error
32+
} catch (\Doctrine\DBAL\Exception\DriverException $e) {
3433
self::assertInstanceOf(DriverException::class, $e);
3534

3635
/* For PDO, we are using ERRMODE EXCEPTION, so this catch should be

0 commit comments

Comments
 (0)