diff --git a/src/Driver/OCI8/Exception/Error.php b/src/Driver/OCI8/Exception/Error.php index 30ed1f8d079..de3265b0d58 100644 --- a/src/Driver/OCI8/Exception/Error.php +++ b/src/Driver/OCI8/Exception/Error.php @@ -36,10 +36,12 @@ public static function new($resource): self //ORA-00001: unique constraint (DOCTRINE.GH3423_UNIQUE) violated [$firstMessage, $secondMessage] = explode("\n", $message, 2); - [$code, $message] = explode(': ', $secondMessage, 2); - $code = (int) str_replace('ORA-', '', $code); + [$causeCode, $causeMessage] = explode(': ', $secondMessage, 2); + $causeCode = (int) str_replace('ORA-', '', $causeCode); + + $cause = new self($causeMessage, null, $causeCode); } - return new self($message, null, $code); + return new self($message, null, $code, $cause ?? null); } } diff --git a/src/Driver/PDO/Exception.php b/src/Driver/PDO/Exception.php index a9208f0b6e9..97310daa2de 100644 --- a/src/Driver/PDO/Exception.php +++ b/src/Driver/PDO/Exception.php @@ -35,10 +35,12 @@ public static function new(PDOException $exception): self //ORA-00001: unique constraint (DOCTRINE.GH3423_UNIQUE) violated [$firstMessage, $secondMessage] = explode("\n", $message, 2); - [$code, $message] = explode(': ', $secondMessage, 2); - $code = (int) str_replace('ORA-', '', $code); + [$causeCode, $causeMessage] = explode(': ', $secondMessage, 2); + $causeCode = (int) str_replace('ORA-', '', $causeCode); + + $cause = new self($causeMessage, null, $causeCode, $exception); } - return new self($message, $sqlState, $code, $exception); + return new self($message, $sqlState, $code, $cause ?? $exception); } } diff --git a/tests/Driver/PDO/ExceptionTest.php b/tests/Driver/PDO/ExceptionTest.php index 9863f1e9c4d..0ae3601882b 100644 --- a/tests/Driver/PDO/ExceptionTest.php +++ b/tests/Driver/PDO/ExceptionTest.php @@ -74,10 +74,12 @@ public function testExposesUnderlyingErrorOnOracle(): void $exception = Exception::new($pdoException); - self::assertSame(1, $exception->getCode()); - self::assertStringContainsString( - 'unique constraint (DOCTRINE.C1_UNIQUE) violated', - $exception->getMessage() - ); + self::assertSame(2091, $exception->getCode()); + self::assertStringContainsString('transaction rolled back', $exception->getMessage()); + + $previous = $exception->getPrevious(); + self::assertNotNull($previous); + self::assertSame(1, $previous->getCode()); + self::assertStringContainsString('unique constraint (DOCTRINE.C1_UNIQUE) violated', $previous->getMessage()); } } diff --git a/tests/Functional/DeferrableConstraintsTest.php b/tests/Functional/DeferrableConstraintsTest.php index 17b876efbfa..7adcfa36b2b 100644 --- a/tests/Functional/DeferrableConstraintsTest.php +++ b/tests/Functional/DeferrableConstraintsTest.php @@ -80,20 +80,25 @@ public function testTransactionalWithDeferredConstraint(): void { $this->skipIfDeferrableIsNotSupported(); - $this->connection->transactional(function (Connection $connection): void { - $connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName)); - - $connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); + try { + $this->connection->transactional(function (Connection $connection): void { + $connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName)); - $this->expectUniqueConstraintViolation(); - }); + $connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); + }); + } catch (Throwable $throwable) { + $this->expectUniqueConstraintViolation($throwable); + } } public function testTransactionalWithNonDeferredConstraint(): void { $this->connection->transactional(function (Connection $connection): void { - $this->expectUniqueConstraintViolation(); - $connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); + try { + $connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); + } catch (Throwable $throwable) { + $this->expectUniqueConstraintViolation($throwable); + } }); } @@ -107,14 +112,16 @@ public function testTransactionalWithDeferredConstraintAndTransactionNesting(): $this->connection->setNestTransactionsWithSavepoints(true); - $this->connection->transactional(function (Connection $connection): void { - $connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName)); - $connection->beginTransaction(); - $connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); - $connection->commit(); - - $this->expectUniqueConstraintViolation(); - }); + try { + $this->connection->transactional(function (Connection $connection): void { + $connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName)); + $connection->beginTransaction(); + $connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); + $connection->commit(); + }); + } catch (Throwable $throwable) { + $this->expectUniqueConstraintViolation($throwable); + } } public function testTransactionalWithNonDeferredConstraintAndTransactionNesting(): void @@ -130,12 +137,10 @@ public function testTransactionalWithNonDeferredConstraintAndTransactionNesting( try { $this->connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); - } catch (Throwable $t) { + } catch (Throwable $throwable) { $this->connection->rollBack(); - $this->expectUniqueConstraintViolation(); - - throw $t; + $this->expectUniqueConstraintViolation($throwable); } }); } @@ -148,8 +153,11 @@ public function testCommitWithDeferredConstraint(): void $this->connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName)); $this->connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); - $this->expectUniqueConstraintViolation(); - $this->connection->commit(); + try { + $this->connection->commit(); + } catch (Throwable $throwable) { + $this->expectUniqueConstraintViolation($throwable); + } } public function testInsertWithNonDeferredConstraint(): void @@ -161,9 +169,7 @@ public function testInsertWithNonDeferredConstraint(): void } catch (Throwable $t) { $this->connection->rollBack(); - $this->expectUniqueConstraintViolation(); - - throw $t; + $this->expectUniqueConstraintViolation($t); } } @@ -183,9 +189,11 @@ public function testCommitWithDeferredConstraintAndTransactionNesting(): void $this->connection->executeStatement('INSERT INTO deferrable_constraints VALUES (1)'); $this->connection->commit(); - $this->expectUniqueConstraintViolation(); - - $this->connection->commit(); + try { + $this->connection->commit(); + } catch (Throwable $throwable) { + $this->expectUniqueConstraintViolation($throwable); + } } public function testCommitWithNonDeferredConstraintAndTransactionNesting(): void @@ -206,9 +214,7 @@ public function testCommitWithNonDeferredConstraintAndTransactionNesting(): void } catch (Throwable $t) { $this->connection->rollBack(); - $this->expectUniqueConstraintViolation(); - - throw $t; + $this->expectUniqueConstraintViolation($t); } } @@ -228,22 +234,34 @@ private function skipIfDeferrableIsNotSupported(): void self::markTestSkipped('Only databases supporting deferrable constraints are eligible for this test.'); } - private function expectUniqueConstraintViolation(): void + private function expectUniqueConstraintViolation(Throwable $throwable): void { if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) { - $this->expectExceptionMessage(sprintf("Violation of UNIQUE KEY constraint '%s'", $this->constraintName)); + self::assertSame( + sprintf("Violation of UNIQUE KEY constraint '%s'", $this->constraintName), + $throwable->getMessage() + ); return; } if ($this->connection->getDatabasePlatform() instanceof DB2Platform) { // No concrete message is provided - $this->expectException(DriverException::class); + self::assertInstanceOf(DriverException::class, $throwable); + + return; + } + + if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + self::assertInstanceOf(DriverException::class, $throwable); + $previous = $throwable->getPrevious(); + $this->assertNotNull($previous); + self::assertInstanceOf(UniqueConstraintViolationException::class, $previous); return; } - $this->expectException(UniqueConstraintViolationException::class); + self::assertInstanceOf(UniqueConstraintViolationException::class, $throwable); } protected function tearDown(): void