Skip to content

Commit

Permalink
Merge branch '4.2.x' into 4.3.x
Browse files Browse the repository at this point in the history
  • Loading branch information
morozov committed Mar 7, 2025
2 parents dea5804 + 33d2d7f commit b2ac865
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 189 deletions.
6 changes: 5 additions & 1 deletion src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,11 @@ public function beginTransaction(): void
++$this->transactionNestingLevel;

if ($this->transactionNestingLevel === 1) {
$connection->beginTransaction();
try {
$connection->beginTransaction();
} catch (Driver\Exception $e) {
throw $this->convertException($e);
}
} else {
$this->createSavepoint($this->_getNestedTransactionSavePointName());
}
Expand Down
5 changes: 5 additions & 0 deletions src/Driver/API/PostgreSQL/ExceptionConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
Expand Down Expand Up @@ -77,6 +78,10 @@ public function convert(Exception $exception, ?Query $query): DriverException
return new ConnectionException($exception, $query);
}

if (str_contains($exception->getMessage(), 'terminating connection')) {
return new ConnectionLost($exception, $query);
}

return new DriverException($exception, $query);
}
}
4 changes: 3 additions & 1 deletion src/Driver/Mysqli/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ public function lastInsertId(): int|string

public function beginTransaction(): void
{
$this->connection->begin_transaction();
if (! $this->connection->begin_transaction()) {
throw ConnectionError::new($this->connection);
}
}

public function commit(): void
Expand Down
1 change: 0 additions & 1 deletion src/Platforms/OraclePlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,6 @@ protected function getCreateAutoincrementSql(string $name, string $table, int $s
WHILE (last_InsertID > last_Sequence) LOOP
SELECT %4$s.NEXTVAL INTO last_Sequence FROM DUAL;
END LOOP;
SELECT %4$s.NEXTVAL INTO last_Sequence FROM DUAL;
END IF;
END;
SQL,
Expand Down
47 changes: 42 additions & 5 deletions tests/Functional/AutoIncrementColumnTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Doctrine\DBAL\Tests\Functional;

use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
Expand All @@ -13,32 +16,66 @@ class AutoIncrementColumnTest extends FunctionalTestCase
{
private bool $shouldDisableIdentityInsert = false;

/** @throws Exception */
protected function setUp(): void
{
$table = new Table('auto_increment_table');
$table->addColumn('id', Types::INTEGER, ['autoincrement' => true]);
$table->addColumn('val', Types::INTEGER);
$table->setPrimaryKey(['id']);

$this->dropAndCreateTable($table);
}

/** @throws Exception */
protected function tearDown(): void
{
if (! $this->shouldDisableIdentityInsert) {
return;
}

$this->connection->executeStatement('SET IDENTITY_INSERT auto_increment_table OFF');
$this->setIdentityInsert('OFF');
}

/** @throws Exception */
public function testInsertAutoGeneratesValue(): void
{
$this->connection->insert('auto_increment_table', ['val' => 0]);
self::assertEquals(1, $this->connection->fetchOne('SELECT MAX(id) FROM auto_increment_table'));
}

/** @throws Exception */
public function testInsertIdentityValue(): void
{
if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) {
$this->connection->executeStatement('SET IDENTITY_INSERT auto_increment_table ON');
$platform = $this->connection->getDatabasePlatform();
$isSQLServer = $platform instanceof SQLServerPlatform;

if ($isSQLServer) {
$this->setIdentityInsert('ON');
$this->shouldDisableIdentityInsert = true;
}

$this->connection->insert('auto_increment_table', ['id' => 2]);
self::assertEquals(2, $this->connection->fetchOne('SELECT id FROM auto_increment_table'));
$this->connection->insert('auto_increment_table', ['id' => 2, 'val' => 0]);
self::assertEquals(2, $this->connection->fetchOne('SELECT MAX(id) FROM auto_increment_table'));

if ($isSQLServer) {
$this->setIdentityInsert('OFF');
$this->shouldDisableIdentityInsert = false;
}

// using an explicit value for an autoincrement column does not affect the next value
// on the following platforms
if ($platform instanceof PostgreSqlPlatform || $platform instanceof DB2Platform) {
return;
}

$this->connection->insert('auto_increment_table', ['val' => 0]);
self::assertEquals(3, $this->connection->fetchOne('SELECT MAX(id) FROM auto_increment_table'));
}

/** @throws Exception */
private function setIdentityInsert(string $value): void
{
$this->connection->executeStatement('SET IDENTITY_INSERT auto_increment_table ' . $value);
}
}
256 changes: 256 additions & 0 deletions tests/Functional/Schema/AlterTableTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\Schema;

use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Types\Types;

class AlterTableTest extends FunctionalTestCase
{
public function testAddPrimaryKeyOnExistingColumn(): void
{
if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) {
self::markTestSkipped(
'SQLite will automatically set up auto-increment behavior on the primary key column, which this test'
. ' does not expect.',
);
}

$table = new Table('alter_pk');
$table->addColumn('id', Types::INTEGER);
$table->addColumn('val', Types::INTEGER);

$this->testMigration($table, static function (Table $table): void {
$table->setPrimaryKey(['id']);
});
}

public function testAddPrimaryKeyOnNewAutoIncrementColumn(): void
{
if ($this->connection->getDatabasePlatform() instanceof DB2Platform) {
self::markTestSkipped(
'IBM DB2 LUW does not support adding identity columns to an existing table.',
);
}

$table = new Table('alter_pk');
$table->addColumn('val', Types::INTEGER);

$this->testMigration($table, static function (Table $table): void {
$table->addColumn('id', Types::INTEGER, ['autoincrement' => true]);
$table->setPrimaryKey(['id']);
});
}

public function testAlterPrimaryKeyFromAutoincrementToNonAutoincrementColumn(): void
{
$platform = $this->connection->getDatabasePlatform();

if ($platform instanceof AbstractMySQLPlatform) {
self::markTestIncomplete(
'DBAL should not allow this migration on MySQL because an auto-increment column must be part of the'
. ' primary key constraint.',
);
}

if ($platform instanceof SQLitePlatform) {
self::markTestSkipped(
'SQLite does not support auto-increment columns that are not part the primary key constraint',
);
}

$this->ensureDroppingPrimaryKeyConstraintIsSupported();

$table = new Table('alter_pk');
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
$table->addColumn('id2', Types::INTEGER);
$table->setPrimaryKey(['id1']);

$this->testMigration($table, static function (Table $table): void {
$table->dropPrimaryKey();
$table->setPrimaryKey(['id2']);
});
}

public function testDropPrimaryKeyWithAutoincrementColumn(): void
{
$platform = $this->connection->getDatabasePlatform();

if ($platform instanceof AbstractMySQLPlatform) {
self::markTestIncomplete(
'DBAL should not allow this migration on MySQL because an auto-increment column must be part of the'
. ' primary key constraint.',
);
}

if ($platform instanceof SQLitePlatform) {
self::markTestSkipped(
'SQLite does not support auto-increment columns as part of composite primary key constraint',
);
}

$this->ensureDroppingPrimaryKeyConstraintIsSupported();

$table = new Table('alter_pk');
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
$table->addColumn('id2', Types::INTEGER);
$table->setPrimaryKey(['id1', 'id2']);

$this->testMigration($table, static function (Table $table): void {
$table->dropPrimaryKey();
});
}

public function testDropNonAutoincrementColumnFromCompositePrimaryKeyWithAutoincrementColumn(): void
{
if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
self::markTestIncomplete(
'DBAL does not restore the auto-increment attribute after dropping and adding the constraint,'
. ' which is a bug.',
);
}

$this->ensureDroppingPrimaryKeyConstraintIsSupported();

$table = new Table('alter_pk');
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
$table->addColumn('id2', Types::INTEGER);
$table->setPrimaryKey(['id1', 'id2']);

$this->testMigration($table, static function (Table $table): void {
$table->dropPrimaryKey();
$table->setPrimaryKey(['id1']);
});
}

public function testAddNonAutoincrementColumnToPrimaryKeyWithAutoincrementColumn(): void
{
$platform = $this->connection->getDatabasePlatform();

if ($platform instanceof AbstractMySQLPlatform) {
self::markTestIncomplete(
'DBAL does not restore the auto-increment attribute after dropping and adding the constraint,'
. ' which is a bug.',
);
}

if ($platform instanceof SQLitePlatform) {
self::markTestSkipped(
'SQLite does not support auto-increment columns as part of composite primary key constraint',
);
}

$this->ensureDroppingPrimaryKeyConstraintIsSupported();

$table = new Table('alter_pk');
$table->addColumn('id1', Types::INTEGER, ['autoincrement' => true]);
$table->addColumn('id2', Types::INTEGER);
$table->setPrimaryKey(['id1']);

$this->testMigration($table, static function (Table $table): void {
$table->dropPrimaryKey();
$table->setPrimaryKey(['id1', 'id2']);
});
}

public function testAddNewColumnToPrimaryKey(): void
{
$this->ensureDroppingPrimaryKeyConstraintIsSupported();

$table = new Table('alter_pk');
$table->addColumn('id1', Types::INTEGER);
$table->setPrimaryKey(['id1']);

$this->testMigration($table, static function (Table $table): void {
$table->addColumn('id2', Types::INTEGER);
$table->dropPrimaryKey();
$table->setPrimaryKey(['id1', 'id2']);
});
}

public function testReplaceForeignKeyConstraint(): void
{
$articles = new Table('articles');
$articles->addColumn('id', Types::INTEGER);
$articles->addColumn('sku', Types::INTEGER);
$articles->setPrimaryKey(['id']);
$articles->addUniqueConstraint(['sku']);

$orders = new Table('orders');
$orders->addColumn('id', Types::INTEGER);
$orders->addColumn('article_id', Types::INTEGER);
$orders->addColumn('article_sku', Types::INTEGER);
$orders->addForeignKeyConstraint(
'articles',
['article_id'],
['id'],
[],
'articles_fk',
);

$this->dropTableIfExists('orders');
$this->dropTableIfExists('articles');

$this->connection->createSchemaManager()
->createTable($articles);

$this->testMigration($orders, static function (Table $table): void {
$table->removeForeignKey('articles_fk');
$table->addForeignKeyConstraint(
'articles',
['article_sku'],
['sku'],
[],
'articles_fk',
);
});
}

private function ensureDroppingPrimaryKeyConstraintIsSupported(): void
{
$platform = $this->connection->getDatabasePlatform();

if (
! ($platform instanceof DB2Platform)
&& ! ($platform instanceof OraclePlatform)
&& ! ($platform instanceof SQLServerPlatform)
) {
return;
}

self::markTestIncomplete(
'Dropping primary key constraint on the currently used database platform is not implemented.',
);
}

private function testMigration(Table $oldTable, callable $migration): void
{
$this->dropAndCreateTable($oldTable);

$newTable = clone $oldTable;

$migration($newTable);

$schemaManager = $this->connection->createSchemaManager();

$diff = $schemaManager->createComparator()
->compareTables($oldTable, $newTable);

$schemaManager->alterTable($diff);

$introspectedTable = $schemaManager->introspectTable($newTable->getName());

$diff = $schemaManager->createComparator()
->compareTables($newTable, $introspectedTable);

self::assertTrue($diff->isEmpty());
}
}
Loading

0 comments on commit b2ac865

Please sign in to comment.