Skip to content

Commit

Permalink
Introduce UniqueConstraintEditor
Browse files Browse the repository at this point in the history
  • Loading branch information
morozov committed Jan 5, 2025
1 parent 08e62ad commit a0dfb21
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 22 deletions.
4 changes: 3 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ The following `UniqueConstraint` methods and property have been deprecated:
Additionally,
1. Extending the `UniqueConstraint` class has been deprecated. Use the `UniqueConstraint` class directly.
2. Instantiation of a unique constraint without columns is deprecated.
3. The `AbstractPlatform::getUniqueConstraintDeclarationSQL()` method has been marked as internal.
3. The `UniqueConstraint` constructor has been marked as internal. Use `UniqueConstraint::editor()` to instantiate an
editor and `UniqueConstraintEditor::create()` to create a unique constraint.
4. The `AbstractPlatform::getUniqueConstraintDeclarationSQL()` method has been marked as internal.

## Deprecated `AbstractAsset::isIdentifierQuoted()`

Expand Down
17 changes: 17 additions & 0 deletions src/Schema/Exception/InvalidUniqueConstraintDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Schema\Exception;

use Doctrine\DBAL\Schema\SchemaException;
use LogicException;

/** @psalm-immutable */
final class InvalidUniqueConstraintDefinition extends LogicException implements SchemaException
{
public static function columnNamesAreNotSet(): self
{
return new self('Unique constraint column names are not set.');
}
}
26 changes: 24 additions & 2 deletions src/Schema/UniqueConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class UniqueConstraint extends AbstractOptionallyNamedObject
private bool $failedToParseColumnNames = false;

/**
* @internal Use {@link UniqueConstraint::editor()} to instantiate an editor and
* {@link UniqueConstraintEditor::create()} to create a unique constraint.
*
* @param non-empty-list<string> $columns
* @param array<string> $flags
* @param array<string, mixed> $options
Expand Down Expand Up @@ -201,7 +204,7 @@ public function getFlags(): array
/**
* Adds flag for a unique constraint that translates to platform specific handling.
*
* @deprecated
* @deprecated Use {@see UniqueConstraintEditor::setIsClustered()} instead.
*
* @return $this
*
Expand Down Expand Up @@ -249,7 +252,7 @@ public function isClustered(): bool
/**
* Removes a flag.
*
* @deprecated
* @deprecated Use {@see UniqueConstraintEditor::setIsClustered()} instead.
*/
public function removeFlag(string $flag): void
{
Expand Down Expand Up @@ -333,4 +336,23 @@ protected function addColumn(string $column): void
);
}
}

/**
* Instantiates a new unique constraint editor.
*/
public static function editor(): UniqueConstraintEditor
{
return new UniqueConstraintEditor();
}

/**
* Instantiates a new unique constraint editor and initializes it with the constraint's properties.
*/
public function edit(): UniqueConstraintEditor

Check warning on line 351 in src/Schema/UniqueConstraint.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/UniqueConstraint.php#L351

Added line #L351 was not covered by tests
{
return self::editor()
->setName($this->getObjectName())
->setColumnNames(...$this->getColumnNames())
->setIsClustered($this->isClustered());

Check warning on line 356 in src/Schema/UniqueConstraint.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/UniqueConstraint.php#L353-L356

Added lines #L353 - L356 were not covered by tests
}
}
62 changes: 62 additions & 0 deletions src/Schema/UniqueConstraintEditor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Schema;

use Doctrine\DBAL\Schema\Exception\InvalidUniqueConstraintDefinition;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;

use function array_map;
use function array_merge;
use function array_values;
use function count;

final class UniqueConstraintEditor
{
private ?UnqualifiedName $name = null;

/** @var list<UnqualifiedName> */
private array $columnNames = [];

private bool $isClustered = false;

/** @internal Use {@link UniqueConstraint::editor()} or {@link UniqueConstraint::edit()} to create an instance */
public function __construct()
{
}

public function setName(?UnqualifiedName $name): self
{
$this->name = $name;

return $this;
}

public function setColumnNames(UnqualifiedName $firstColumName, UnqualifiedName ...$otherColumnNames): self
{
$this->columnNames = array_merge([$firstColumName], array_values($otherColumnNames));

return $this;
}

public function setIsClustered(bool $isClustered): self
{
$this->isClustered = $isClustered;

return $this;
}

public function create(): UniqueConstraint
{
if (count($this->columnNames) < 1) {
throw InvalidUniqueConstraintDefinition::columnNamesAreNotSet();
}

return new UniqueConstraint(
$this->name?->toString() ?? '',
array_map(static fn (UnqualifiedName $columnName) => $columnName->toString(), $this->columnNames),
$this->isClustered ? ['clustered'] : [],
);
}
}
12 changes: 11 additions & 1 deletion tests/Functional/Schema/SchemaManagerFunctionalTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\DBAL\Schema\SchemaException;
Expand Down Expand Up @@ -435,7 +437,15 @@ public function testDropAndCreateUniqueConstraint(): void
$table->addColumn('id', Types::INTEGER);
$this->dropAndCreateTable($table);

$uniqueConstraint = new UniqueConstraint('uniq_id', ['id']);
$uniqueConstraint = UniqueConstraint::editor()
->setName(
new UnqualifiedName(Identifier::unquoted('uniq_id')),
)
->setColumnNames(
new UnqualifiedName(Identifier::unquoted('id')),
)
->create();

$this->schemaManager->createUniqueConstraint($uniqueConstraint, $table->getName());

// there's currently no API for introspecting unique constraints,
Expand Down
10 changes: 8 additions & 2 deletions tests/Functional/Schema/UniqueConstraintTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Doctrine\DBAL\Tests\Functional\Schema;

use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\UniqueConstraint;
use Doctrine\DBAL\Tests\FunctionalTestCase;
Expand All @@ -22,8 +24,12 @@ public function testUnnamedUniqueConstraint(): void
new Column('username', Type::getType(Types::STRING), ['length' => 32]),
new Column('email', Type::getType(Types::STRING), ['length' => 255]),
], [], [
new UniqueConstraint('', ['username']),
new UniqueConstraint('', ['email']),
UniqueConstraint::editor()
->setColumnNames(new UnqualifiedName(Identifier::unquoted('username')))
->create(),
UniqueConstraint::editor()
->setColumnNames(new UnqualifiedName(Identifier::unquoted('email')))
->create(),
], []);

$sm = $this->connection->createSchemaManager();
Expand Down
17 changes: 7 additions & 10 deletions tests/Functional/UniqueConstraintViolationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\UniqueConstraint;
use Doctrine\DBAL\Tests\FunctionalTestCase;
Expand All @@ -33,8 +35,6 @@ protected function setUp(): void

if ($platform instanceof OraclePlatform) {
$constraintName = 'C1_UNIQUE';
} elseif ($platform instanceof PostgreSQLPlatform) {
$constraintName = 'c1_unique';
} else {
$constraintName = 'c1_unique';
}
Expand All @@ -47,13 +47,7 @@ protected function setUp(): void
$table->addColumn('unique_field', 'integer', ['notnull' => true]);
$schemaManager->createTable($table);

if ($platform instanceof OraclePlatform) {
$createConstraint = sprintf(
'ALTER TABLE unique_constraint_violations ' .
'ADD CONSTRAINT %s UNIQUE (unique_field) DEFERRABLE INITIALLY IMMEDIATE',
$constraintName,
);
} elseif ($platform instanceof PostgreSQLPlatform) {
if ($platform instanceof OraclePlatform || $platform instanceof PostgreSQLPlatform) {
$createConstraint = sprintf(
'ALTER TABLE unique_constraint_violations ' .
'ADD CONSTRAINT %s UNIQUE (unique_field) DEFERRABLE INITIALLY IMMEDIATE',
Expand All @@ -65,7 +59,10 @@ protected function setUp(): void
$constraintName,
);
} else {
$createConstraint = new UniqueConstraint($constraintName, ['unique_field']);
$createConstraint = UniqueConstraint::editor()
->setName(new UnqualifiedName(Identifier::unquoted($constraintName)))
->setColumnNames(new UnqualifiedName(Identifier::unquoted('unique_field')))
->create();
}

if ($createConstraint instanceof UniqueConstraint) {
Expand Down
10 changes: 9 additions & 1 deletion tests/Platforms/AbstractPlatformTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Doctrine\DBAL\Schema\ComparatorConfig;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\UniqueConstraint;
Expand Down Expand Up @@ -188,7 +190,13 @@ public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes()
{
$where = 'test IS NULL AND test2 IS NOT NULL';
$indexDef = new Index('name', ['test', 'test2'], false, false, [], ['where' => $where]);
$uniqueConstraint = new UniqueConstraint('name', ['test', 'test2'], [], []);
$uniqueConstraint = UniqueConstraint::editor()
->setName(new UnqualifiedName(Identifier::unquoted('name')))
->setColumnNames(
new UnqualifiedName(Identifier::unquoted('test')),
new UnqualifiedName(Identifier::unquoted('test2')),
)
->create();

$expected = ' WHERE ' . $where;

Expand Down
38 changes: 38 additions & 0 deletions tests/Schema/UniqueConstraintEditorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Schema;

use Doctrine\DBAL\Schema\Exception\InvalidUniqueConstraintDefinition;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\UniqueConstraint;
use PHPUnit\Framework\TestCase;

class UniqueConstraintEditorTest extends TestCase
{
public function testEmptyColumnNames(): void
{
$this->expectException(InvalidUniqueConstraintDefinition::class);

UniqueConstraint::editor()
->create();
}

public function testSetIsClustered(): void
{
$columnName = new UnqualifiedName(Identifier::unquoted('user_id'));

$editor = UniqueConstraint::editor()
->setColumnNames($columnName);

$constraint = $editor->create();
self::assertFalse($constraint->isClustered());

$constraint = $editor
->setIsClustered(true)
->create();
self::assertTrue($constraint->isClustered());
}
}
19 changes: 14 additions & 5 deletions tests/Schema/UniqueConstraintTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,26 @@ class UniqueConstraintTest extends TestCase
/** @throws Exception */
public function testGetNonNullObjectName(): void
{
$uniqueConstraint = new UniqueConstraint('uq_user_id', ['user_id']);
$name = $uniqueConstraint->getObjectName();
$name = new UnqualifiedName(Identifier::unquoted('uq_user_id'));

self::assertNotNull($name);
self::assertEquals(Identifier::unquoted('uq_user_id'), $name->getIdentifier());
$uniqueConstraint = UniqueConstraint::editor()
->setName($name)
->setColumnNames(
new UnqualifiedName(Identifier::unquoted('user_id')),
)
->create();

self::assertEquals($name, $uniqueConstraint->getObjectName());
}

/** @throws Exception */
public function testGetNullObjectName(): void
{
$uniqueConstraint = new UniqueConstraint('', ['user_id']);
$uniqueConstraint = UniqueConstraint::editor()
->setColumnNames(
new UnqualifiedName(Identifier::unquoted('user_id')),
)
->create();

self::assertNull($uniqueConstraint->getObjectName());
}
Expand Down

0 comments on commit a0dfb21

Please sign in to comment.