Skip to content

Commit 09ed614

Browse files
committed
Reproduce
1 parent c204fe1 commit 09ed614

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Tests\Functional;
6+
7+
use Doctrine\DBAL\Connection;
8+
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
9+
use Doctrine\DBAL\Driver\PDO\PDOException;
10+
use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as PDOPgSQLDriver;
11+
use Doctrine\DBAL\Driver\PgSQL\Driver as PgSQLDriver;
12+
use Doctrine\DBAL\Driver\PgSQL\Exception as PgSQLException;
13+
use Doctrine\DBAL\Exception\DriverException;
14+
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
15+
use Doctrine\DBAL\Platforms\DB2Platform;
16+
use Doctrine\DBAL\Platforms\OraclePlatform;
17+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
18+
use Doctrine\DBAL\Platforms\SqlitePlatform;
19+
use Doctrine\DBAL\Platforms\SQLServerPlatform;
20+
use Doctrine\DBAL\Schema\Table;
21+
use Doctrine\DBAL\Schema\UniqueConstraint;
22+
use Doctrine\DBAL\Tests\FunctionalTestCase;
23+
use PHPUnit\Framework\Assert;
24+
use Throwable;
25+
26+
use function sprintf;
27+
28+
final class UniqueConstraintViolationsTest extends FunctionalTestCase
29+
{
30+
private string $constraintName = '';
31+
32+
protected function setUp(): void
33+
{
34+
parent::setUp();
35+
36+
$platform = $this->connection->getDatabasePlatform();
37+
38+
if ($platform instanceof OraclePlatform) {
39+
$constraintName = 'C1_UNIQUE';
40+
} elseif ($platform instanceof PostgreSQLPlatform) {
41+
$constraintName = 'c1_unique';
42+
} else {
43+
$constraintName = 'c1_unique';
44+
}
45+
46+
$this->constraintName = $constraintName;
47+
48+
$schemaManager = $this->connection->createSchemaManager();
49+
50+
$table = new Table('unique_constraint_violations');
51+
$table->addColumn('unique_field', 'integer', ['notnull' => true]);
52+
$schemaManager->createTable($table);
53+
54+
if ($platform instanceof OraclePlatform) {
55+
$createConstraint = sprintf(
56+
'ALTER TABLE unique_constraint_violations ' .
57+
'ADD CONSTRAINT %s UNIQUE (unique_field) DEFERRABLE INITIALLY IMMEDIATE',
58+
$constraintName,
59+
);
60+
} elseif ($platform instanceof PostgreSQLPlatform) {
61+
$createConstraint = sprintf(
62+
'ALTER TABLE unique_constraint_violations ' .
63+
'ADD CONSTRAINT %s UNIQUE (unique_field) DEFERRABLE INITIALLY IMMEDIATE',
64+
$constraintName,
65+
);
66+
} elseif ($platform instanceof SqlitePlatform) {
67+
$createConstraint = sprintf(
68+
'CREATE UNIQUE INDEX %s ON unique_constraint_violations(unique_field)',
69+
$constraintName,
70+
);
71+
} else {
72+
$createConstraint = new UniqueConstraint($constraintName, ['unique_field']);
73+
}
74+
75+
if ($createConstraint instanceof UniqueConstraint) {
76+
$schemaManager->createUniqueConstraint($createConstraint, 'unique_constraint_violations');
77+
} else {
78+
$this->connection->executeStatement($createConstraint);
79+
}
80+
81+
$this->connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
82+
}
83+
84+
public function testTransactionalViolatesDeferredConstraint(): void
85+
{
86+
$this->skipIfDeferrableIsNotSupported();
87+
88+
$this->connection->transactional(function (Connection $connection): void {
89+
$connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName));
90+
91+
$connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
92+
93+
$this->expectUniqueConstraintViolation(true);
94+
});
95+
}
96+
97+
public function testTransactionalViolatesConstraint(): void
98+
{
99+
$this->connection->transactional(function (Connection $connection): void {
100+
$this->expectUniqueConstraintViolation(false);
101+
$connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
102+
});
103+
}
104+
105+
public function testTransactionalViolatesDeferredConstraintWhileUsingTransactionNesting(): void
106+
{
107+
if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
108+
self::markTestSkipped('This test requires the platform to support savepoints.');
109+
}
110+
111+
$this->skipIfDeferrableIsNotSupported();
112+
113+
$this->connection->setNestTransactionsWithSavepoints(true);
114+
115+
$this->connection->transactional(function (Connection $connection): void {
116+
$connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName));
117+
$connection->beginTransaction();
118+
$connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
119+
$connection->commit();
120+
121+
$this->expectUniqueConstraintViolation(true);
122+
});
123+
}
124+
125+
public function testTransactionalViolatesConstraintWhileUsingTransactionNesting(): void
126+
{
127+
if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
128+
self::markTestSkipped('This test requires the platform to support savepoints.');
129+
}
130+
131+
$this->connection->setNestTransactionsWithSavepoints(true);
132+
133+
$this->connection->transactional(function (Connection $connection): void {
134+
$connection->beginTransaction();
135+
136+
try {
137+
$this->connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
138+
} catch (Throwable $t) {
139+
$this->connection->rollBack();
140+
141+
$this->expectUniqueConstraintViolation(false);
142+
143+
throw $t;
144+
}
145+
});
146+
}
147+
148+
public function testCommitViolatesDeferredConstraint(): void
149+
{
150+
$this->skipIfDeferrableIsNotSupported();
151+
152+
$this->connection->beginTransaction();
153+
$this->connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName));
154+
$this->connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
155+
156+
$this->expectUniqueConstraintViolation(true);
157+
$this->connection->commit();
158+
}
159+
160+
public function testInsertViolatesConstraint(): void
161+
{
162+
$this->connection->beginTransaction();
163+
164+
try {
165+
$this->connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
166+
} catch (Throwable $t) {
167+
$this->connection->rollBack();
168+
169+
$this->expectUniqueConstraintViolation(false);
170+
171+
throw $t;
172+
}
173+
}
174+
175+
public function testCommitViolatesDeferredConstraintWhileUsingTransactionNesting(): void
176+
{
177+
if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
178+
self::markTestSkipped('This test requires the platform to support savepoints.');
179+
}
180+
181+
$this->skipIfDeferrableIsNotSupported();
182+
183+
$this->connection->setNestTransactionsWithSavepoints(true);
184+
185+
$this->connection->beginTransaction();
186+
$this->connection->executeStatement(sprintf('SET CONSTRAINTS "%s" DEFERRED', $this->constraintName));
187+
$this->connection->beginTransaction();
188+
$this->connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
189+
$this->connection->commit();
190+
191+
$this->expectUniqueConstraintViolation(true);
192+
193+
$this->connection->commit();
194+
}
195+
196+
public function testCommitViolatesConstraintWhileUsingTransactionNesting(): void
197+
{
198+
if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
199+
self::markTestSkipped('This test requires the platform to support savepoints.');
200+
}
201+
202+
$this->skipIfDeferrableIsNotSupported();
203+
204+
$this->connection->setNestTransactionsWithSavepoints(true);
205+
206+
$this->connection->beginTransaction();
207+
$this->connection->beginTransaction();
208+
209+
try {
210+
$this->connection->executeStatement('INSERT INTO unique_constraint_violations VALUES (1)');
211+
} catch (Throwable $t) {
212+
$this->connection->rollBack();
213+
214+
$this->expectUniqueConstraintViolation(false);
215+
216+
throw $t;
217+
}
218+
}
219+
220+
private function supportsDeferrableConstraints(): bool
221+
{
222+
$platform = $this->connection->getDatabasePlatform();
223+
224+
return $platform instanceof OraclePlatform || $platform instanceof PostgreSQLPlatform;
225+
}
226+
227+
private function skipIfDeferrableIsNotSupported(): void
228+
{
229+
if ($this->supportsDeferrableConstraints()) {
230+
return;
231+
}
232+
233+
self::markTestSkipped('Only databases supporting deferrable constraints are eligible for this test.');
234+
}
235+
236+
private function expectUniqueConstraintViolation(bool $deferred): void
237+
{
238+
if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) {
239+
$this->expectExceptionMessage(sprintf("Violation of UNIQUE KEY constraint '%s'", $this->constraintName));
240+
241+
return;
242+
}
243+
244+
if ($this->connection->getDatabasePlatform() instanceof DB2Platform) {
245+
// No concrete message is provided
246+
$this->expectException(DriverException::class);
247+
248+
return;
249+
}
250+
251+
if ($deferred) {
252+
if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
253+
$this->expectExceptionMessageMatches(
254+
sprintf('~unique constraint \(.+\.%s\) violated~', $this->constraintName),
255+
);
256+
257+
return;
258+
}
259+
260+
$driver = $this->connection->getDriver();
261+
if ($driver instanceof AbstractPostgreSQLDriver) {
262+
$this->expectExceptionMessageMatches(
263+
sprintf('~duplicate key value violates unique constraint "%s"~', $this->constraintName),
264+
);
265+
266+
if ($driver instanceof PDOPgSQLDriver) {
267+
$this->expectException(PDOException::class);
268+
269+
return;
270+
}
271+
272+
if ($driver instanceof PgSQLDriver) {
273+
$this->expectException(PgSQLException::class);
274+
275+
return;
276+
}
277+
278+
Assert::fail('Unsupported PG driver');
279+
}
280+
281+
Assert::fail('Unsupported platform');
282+
} else {
283+
$this->expectException(UniqueConstraintViolationException::class);
284+
}
285+
}
286+
287+
protected function tearDown(): void
288+
{
289+
$schemaManager = $this->connection->createSchemaManager();
290+
$schemaManager->dropTable('unique_constraint_violations');
291+
292+
$this->markConnectionNotReusable();
293+
294+
parent::tearDown();
295+
}
296+
}

0 commit comments

Comments
 (0)