Skip to content

Commit b2ac865

Browse files
committed
Merge branch '4.2.x' into 4.3.x
2 parents dea5804 + 33d2d7f commit b2ac865

File tree

10 files changed

+367
-189
lines changed

10 files changed

+367
-189
lines changed

src/Connection.php

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

10511051
if ($this->transactionNestingLevel === 1) {
1052-
$connection->beginTransaction();
1052+
try {
1053+
$connection->beginTransaction();
1054+
} catch (Driver\Exception $e) {
1055+
throw $this->convertException($e);
1056+
}
10531057
} else {
10541058
$this->createSavepoint($this->_getNestedTransactionSavePointName());
10551059
}

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

src/Platforms/OraclePlatform.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,6 @@ protected function getCreateAutoincrementSql(string $name, string $table, int $s
411411
WHILE (last_InsertID > last_Sequence) LOOP
412412
SELECT %4$s.NEXTVAL INTO last_Sequence FROM DUAL;
413413
END LOOP;
414-
SELECT %4$s.NEXTVAL INTO last_Sequence FROM DUAL;
415414
END IF;
416415
END;
417416
SQL,

tests/Functional/AutoIncrementColumnTest.php

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Doctrine\DBAL\Tests\Functional;
66

7+
use Doctrine\DBAL\Exception;
8+
use Doctrine\DBAL\Platforms\DB2Platform;
9+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
710
use Doctrine\DBAL\Platforms\SQLServerPlatform;
811
use Doctrine\DBAL\Schema\Table;
912
use Doctrine\DBAL\Tests\FunctionalTestCase;
@@ -13,32 +16,66 @@ class AutoIncrementColumnTest extends FunctionalTestCase
1316
{
1417
private bool $shouldDisableIdentityInsert = false;
1518

19+
/** @throws Exception */
1620
protected function setUp(): void
1721
{
1822
$table = new Table('auto_increment_table');
1923
$table->addColumn('id', Types::INTEGER, ['autoincrement' => true]);
24+
$table->addColumn('val', Types::INTEGER);
2025
$table->setPrimaryKey(['id']);
2126

2227
$this->dropAndCreateTable($table);
2328
}
2429

30+
/** @throws Exception */
2531
protected function tearDown(): void
2632
{
2733
if (! $this->shouldDisableIdentityInsert) {
2834
return;
2935
}
3036

31-
$this->connection->executeStatement('SET IDENTITY_INSERT auto_increment_table OFF');
37+
$this->setIdentityInsert('OFF');
3238
}
3339

40+
/** @throws Exception */
41+
public function testInsertAutoGeneratesValue(): void
42+
{
43+
$this->connection->insert('auto_increment_table', ['val' => 0]);
44+
self::assertEquals(1, $this->connection->fetchOne('SELECT MAX(id) FROM auto_increment_table'));
45+
}
46+
47+
/** @throws Exception */
3448
public function testInsertIdentityValue(): void
3549
{
36-
if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) {
37-
$this->connection->executeStatement('SET IDENTITY_INSERT auto_increment_table ON');
50+
$platform = $this->connection->getDatabasePlatform();
51+
$isSQLServer = $platform instanceof SQLServerPlatform;
52+
53+
if ($isSQLServer) {
54+
$this->setIdentityInsert('ON');
3855
$this->shouldDisableIdentityInsert = true;
3956
}
4057

41-
$this->connection->insert('auto_increment_table', ['id' => 2]);
42-
self::assertEquals(2, $this->connection->fetchOne('SELECT id FROM auto_increment_table'));
58+
$this->connection->insert('auto_increment_table', ['id' => 2, 'val' => 0]);
59+
self::assertEquals(2, $this->connection->fetchOne('SELECT MAX(id) FROM auto_increment_table'));
60+
61+
if ($isSQLServer) {
62+
$this->setIdentityInsert('OFF');
63+
$this->shouldDisableIdentityInsert = false;
64+
}
65+
66+
// using an explicit value for an autoincrement column does not affect the next value
67+
// on the following platforms
68+
if ($platform instanceof PostgreSqlPlatform || $platform instanceof DB2Platform) {
69+
return;
70+
}
71+
72+
$this->connection->insert('auto_increment_table', ['val' => 0]);
73+
self::assertEquals(3, $this->connection->fetchOne('SELECT MAX(id) FROM auto_increment_table'));
74+
}
75+
76+
/** @throws Exception */
77+
private function setIdentityInsert(string $value): void
78+
{
79+
$this->connection->executeStatement('SET IDENTITY_INSERT auto_increment_table ' . $value);
4380
}
4481
}
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Tests\Functional\Schema;
6+
7+
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
8+
use Doctrine\DBAL\Platforms\DB2Platform;
9+
use Doctrine\DBAL\Platforms\OraclePlatform;
10+
use Doctrine\DBAL\Platforms\SQLitePlatform;
11+
use Doctrine\DBAL\Platforms\SQLServerPlatform;
12+
use Doctrine\DBAL\Schema\Table;
13+
use Doctrine\DBAL\Tests\FunctionalTestCase;
14+
use Doctrine\DBAL\Types\Types;
15+
16+
class AlterTableTest extends FunctionalTestCase
17+
{
18+
public function testAddPrimaryKeyOnExistingColumn(): void
19+
{
20+
if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) {
21+
self::markTestSkipped(
22+
'SQLite will automatically set up auto-increment behavior on the primary key column, which this test'
23+
. ' does not expect.',
24+
);
25+
}
26+
27+
$table = new Table('alter_pk');
28+
$table->addColumn('id', Types::INTEGER);
29+
$table->addColumn('val', Types::INTEGER);
30+
31+
$this->testMigration($table, static function (Table $table): void {
32+
$table->setPrimaryKey(['id']);
33+
});
34+
}
35+
36+
public function testAddPrimaryKeyOnNewAutoIncrementColumn(): void
37+
{
38+
if ($this->connection->getDatabasePlatform() instanceof DB2Platform) {
39+
self::markTestSkipped(
40+
'IBM DB2 LUW does not support adding identity columns to an existing table.',
41+
);
42+
}
43+
44+
$table = new Table('alter_pk');
45+
$table->addColumn('val', Types::INTEGER);
46+
47+
$this->testMigration($table, static function (Table $table): void {
48+
$table->addColumn('id', Types::INTEGER, ['autoincrement' => true]);
49+
$table->setPrimaryKey(['id']);
50+
});
51+
}
52+
53+
public function testAlterPrimaryKeyFromAutoincrementToNonAutoincrementColumn(): void
54+
{
55+
$platform = $this->connection->getDatabasePlatform();
56+
57+
if ($platform instanceof AbstractMySQLPlatform) {
58+
self::markTestIncomplete(
59+
'DBAL should not allow this migration on MySQL because an auto-increment column must be part of the'
60+
. ' primary key constraint.',
61+
);
62+
}
63+
64+
if ($platform instanceof SQLitePlatform) {
65+
self::markTestSkipped(
66+
'SQLite does not support auto-increment columns that are not part the primary key constraint',
67+
);
68+
}
69+
70+
$this->ensureDroppingPrimaryKeyConstraintIsSupported();
71+
72+
$table = new Table('alter_pk');
73+
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
74+
$table->addColumn('id2', Types::INTEGER);
75+
$table->setPrimaryKey(['id1']);
76+
77+
$this->testMigration($table, static function (Table $table): void {
78+
$table->dropPrimaryKey();
79+
$table->setPrimaryKey(['id2']);
80+
});
81+
}
82+
83+
public function testDropPrimaryKeyWithAutoincrementColumn(): void
84+
{
85+
$platform = $this->connection->getDatabasePlatform();
86+
87+
if ($platform instanceof AbstractMySQLPlatform) {
88+
self::markTestIncomplete(
89+
'DBAL should not allow this migration on MySQL because an auto-increment column must be part of the'
90+
. ' primary key constraint.',
91+
);
92+
}
93+
94+
if ($platform instanceof SQLitePlatform) {
95+
self::markTestSkipped(
96+
'SQLite does not support auto-increment columns as part of composite primary key constraint',
97+
);
98+
}
99+
100+
$this->ensureDroppingPrimaryKeyConstraintIsSupported();
101+
102+
$table = new Table('alter_pk');
103+
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
104+
$table->addColumn('id2', Types::INTEGER);
105+
$table->setPrimaryKey(['id1', 'id2']);
106+
107+
$this->testMigration($table, static function (Table $table): void {
108+
$table->dropPrimaryKey();
109+
});
110+
}
111+
112+
public function testDropNonAutoincrementColumnFromCompositePrimaryKeyWithAutoincrementColumn(): void
113+
{
114+
if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
115+
self::markTestIncomplete(
116+
'DBAL does not restore the auto-increment attribute after dropping and adding the constraint,'
117+
. ' which is a bug.',
118+
);
119+
}
120+
121+
$this->ensureDroppingPrimaryKeyConstraintIsSupported();
122+
123+
$table = new Table('alter_pk');
124+
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
125+
$table->addColumn('id2', Types::INTEGER);
126+
$table->setPrimaryKey(['id1', 'id2']);
127+
128+
$this->testMigration($table, static function (Table $table): void {
129+
$table->dropPrimaryKey();
130+
$table->setPrimaryKey(['id1']);
131+
});
132+
}
133+
134+
public function testAddNonAutoincrementColumnToPrimaryKeyWithAutoincrementColumn(): void
135+
{
136+
$platform = $this->connection->getDatabasePlatform();
137+
138+
if ($platform instanceof AbstractMySQLPlatform) {
139+
self::markTestIncomplete(
140+
'DBAL does not restore the auto-increment attribute after dropping and adding the constraint,'
141+
. ' which is a bug.',
142+
);
143+
}
144+
145+
if ($platform instanceof SQLitePlatform) {
146+
self::markTestSkipped(
147+
'SQLite does not support auto-increment columns as part of composite primary key constraint',
148+
);
149+
}
150+
151+
$this->ensureDroppingPrimaryKeyConstraintIsSupported();
152+
153+
$table = new Table('alter_pk');
154+
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
155+
$table->addColumn('id2', Types::INTEGER);
156+
$table->setPrimaryKey(['id1']);
157+
158+
$this->testMigration($table, static function (Table $table): void {
159+
$table->dropPrimaryKey();
160+
$table->setPrimaryKey(['id1', 'id2']);
161+
});
162+
}
163+
164+
public function testAddNewColumnToPrimaryKey(): void
165+
{
166+
$this->ensureDroppingPrimaryKeyConstraintIsSupported();
167+
168+
$table = new Table('alter_pk');
169+
$table->addColumn('id1', Types::INTEGER);
170+
$table->setPrimaryKey(['id1']);
171+
172+
$this->testMigration($table, static function (Table $table): void {
173+
$table->addColumn('id2', Types::INTEGER);
174+
$table->dropPrimaryKey();
175+
$table->setPrimaryKey(['id1', 'id2']);
176+
});
177+
}
178+
179+
public function testReplaceForeignKeyConstraint(): void
180+
{
181+
$articles = new Table('articles');
182+
$articles->addColumn('id', Types::INTEGER);
183+
$articles->addColumn('sku', Types::INTEGER);
184+
$articles->setPrimaryKey(['id']);
185+
$articles->addUniqueConstraint(['sku']);
186+
187+
$orders = new Table('orders');
188+
$orders->addColumn('id', Types::INTEGER);
189+
$orders->addColumn('article_id', Types::INTEGER);
190+
$orders->addColumn('article_sku', Types::INTEGER);
191+
$orders->addForeignKeyConstraint(
192+
'articles',
193+
['article_id'],
194+
['id'],
195+
[],
196+
'articles_fk',
197+
);
198+
199+
$this->dropTableIfExists('orders');
200+
$this->dropTableIfExists('articles');
201+
202+
$this->connection->createSchemaManager()
203+
->createTable($articles);
204+
205+
$this->testMigration($orders, static function (Table $table): void {
206+
$table->removeForeignKey('articles_fk');
207+
$table->addForeignKeyConstraint(
208+
'articles',
209+
['article_sku'],
210+
['sku'],
211+
[],
212+
'articles_fk',
213+
);
214+
});
215+
}
216+
217+
private function ensureDroppingPrimaryKeyConstraintIsSupported(): void
218+
{
219+
$platform = $this->connection->getDatabasePlatform();
220+
221+
if (
222+
! ($platform instanceof DB2Platform)
223+
&& ! ($platform instanceof OraclePlatform)
224+
&& ! ($platform instanceof SQLServerPlatform)
225+
) {
226+
return;
227+
}
228+
229+
self::markTestIncomplete(
230+
'Dropping primary key constraint on the currently used database platform is not implemented.',
231+
);
232+
}
233+
234+
private function testMigration(Table $oldTable, callable $migration): void
235+
{
236+
$this->dropAndCreateTable($oldTable);
237+
238+
$newTable = clone $oldTable;
239+
240+
$migration($newTable);
241+
242+
$schemaManager = $this->connection->createSchemaManager();
243+
244+
$diff = $schemaManager->createComparator()
245+
->compareTables($oldTable, $newTable);
246+
247+
$schemaManager->alterTable($diff);
248+
249+
$introspectedTable = $schemaManager->introspectTable($newTable->getName());
250+
251+
$diff = $schemaManager->createComparator()
252+
->compareTables($newTable, $introspectedTable);
253+
254+
self::assertTrue($diff->isEmpty());
255+
}
256+
}

0 commit comments

Comments
 (0)