Skip to content

Commit f201a2f

Browse files
committed
add enum remove labels support
1 parent 9c81ccc commit f201a2f

File tree

7 files changed

+181
-38
lines changed

7 files changed

+181
-38
lines changed

src/DBAL/Platform/PostgreSQLPlatform.php

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,29 @@ public function createSchemaManager(Connection $connection): PostgreSQLSchemaMan
2323

2424
public function getListEnumTypesSQL(): string
2525
{
26-
return 'SELECT pg_type.typname AS name,
27-
pg_enum.enumlabel AS label,
28-
pg_description.description AS comment
29-
FROM pg_type
30-
JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid
31-
LEFT JOIN pg_description on pg_description.objoid = pg_type.oid
32-
ORDER BY pg_enum.enumsortorder';
26+
return 'WITH types AS (
27+
SELECT pg_type.typname AS name,
28+
pg_enum.enumlabel AS label,
29+
pg_enum.enumsortorder AS label_order,
30+
pg_description.description AS comment,
31+
pg_class.relname AS usage_table,
32+
pg_attribute.attname AS usage_column,
33+
PG_GET_EXPR(pg_attrdef.adbin, pg_attrdef.adrelid) AS usage_default
34+
FROM pg_type
35+
JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid
36+
LEFT JOIN pg_description ON pg_description.objoid = pg_type.oid
37+
LEFT JOIN pg_depend ON pg_depend.refobjid = pg_type.oid
38+
LEFT JOIN pg_class ON pg_class.oid = pg_depend.objid
39+
LEFT JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND pg_attribute.atttypid = pg_type.oid
40+
LEFT JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
41+
)
42+
SELECT types.name,
43+
types.comment,
44+
JSON_AGG(DISTINCT JSONB_BUILD_OBJECT(\'label\', types.label, \'order\', types.label_order)) AS labels,
45+
JSON_AGG(DISTINCT JSONB_BUILD_OBJECT(\'table\', types.usage_table, \'column\', types.usage_column, \'default\', types.usage_default)) FILTER (WHERE types.usage_table IS NOT NULL AND types.usage_column IS NOT NULL) AS usages
46+
FROM types
47+
GROUP BY types.name, types.comment'
48+
;
3349
}
3450

3551
/**
@@ -59,13 +75,48 @@ public function getAlterTypeSql(EnumTypeAsset $from, EnumTypeAsset $to): array
5975
$toLabels = $to->getLabels();
6076

6177
$result = [];
78+
$typeName = $to->getQuotedName($this);
79+
6280
foreach (array_diff($toLabels, $fromLabels) as $label) {
63-
$result[] = "ALTER TYPE {$to->getQuotedName($this)} ADD VALUE {$this->quoteEnumLabel($label)}";
81+
$result[] = "ALTER TYPE {$typeName} ADD VALUE {$this->quoteEnumLabel($label)}";
6482
}
6583

66-
if (count(array_diff($fromLabels, $toLabels)) > 0) {
67-
throw new Exception('Enum labels reduction is not supported in automatic generation');
84+
$removedLabels = array_diff($fromLabels, $toLabels);
85+
86+
if (count($removedLabels) < 1) {
87+
return $result;
6888
}
89+
90+
$self = $this;
91+
92+
$result[] = "ALTER TYPE {$typeName} RENAME TO {$typeName}_old";
93+
$result[] = $this->getCreateTypeSql($to);
94+
$result[] = $this->getCommentOnTypeSql($to);
95+
96+
foreach ($to->getUsages() as $usage) {
97+
$tableName = $this->quoteIdentifier($usage->getTable());
98+
$columnName = $this->quoteIdentifier($usage->getColumn());
99+
if (($default = $usage->getDefault()) !== null) {
100+
$result[] = sprintf('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT', $tableName, $columnName);
101+
}
102+
$result[] = sprintf(
103+
'ALTER TABLE %1$s ALTER COLUMN %2$s TYPE %3$s USING LOWER(%2$s::text)::%3$s',
104+
$tableName,
105+
$columnName,
106+
$typeName
107+
);
108+
109+
if ($default !== null) {
110+
$result[] = sprintf(
111+
'ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s',
112+
$tableName,
113+
$columnName,
114+
$this->quoteEnumLabel($default)
115+
);
116+
}
117+
}
118+
119+
$result[] = "DROP TYPE {$typeName}_old";
69120

70121
return $result;
71122
}

src/DBAL/Schema/EnumTypeAsset.php

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,23 @@
1414
final class EnumTypeAsset extends AbstractAsset
1515
{
1616
/**
17-
* @var array<int|string>
17+
* @var string[]
1818
*/
1919
private array $labels;
2020
private string $enumClass;
21+
/**
22+
* @var EnumTypeUsageAsset[]
23+
*/
24+
private array $usages;
2125

2226
/**
2327
* @param string $name
2428
* @param string $className
25-
* @param array<int|string> $labels
29+
* @param string[] $labels
30+
* @param EnumTypeUsageAsset[] $usages
2631
* @throws InvalidArgumentException
2732
*/
28-
public function __construct(string $name, string $className, array $labels = [])
33+
public function __construct(string $name, string $className, array $labels = [], array $usages = [])
2934
{
3035
if ($name === '') {
3136
throw new InvalidArgumentException('Invalid custom type name specified');
@@ -34,24 +39,25 @@ public function __construct(string $name, string $className, array $labels = [])
3439
$this->_setName($name);
3540
$this->enumClass = $className;
3641
$this->labels = $labels;
42+
$this->usages = $usages;
3743
}
3844

3945
/**
4046
* @param class-string<EnumInterface|\UnitEnum> $className
4147
* @throws InvalidArgumentException
4248
* @return static
4349
*/
44-
public static function fromEnumClassName(string $className): self
50+
public static function fromEnumClassName(string $name, string $className): self
4551
{
4652
return new self(
47-
EnumTool::getEnumTypeNameFromClassName($className),
53+
$name,
4854
$className,
4955
EnumTool::getEnumLabelsByClassName($className)
5056
);
5157
}
5258

5359
/**
54-
* @return array<int|string>
60+
* @return string[]
5561
*/
5662
public function getLabels(): array
5763
{
@@ -63,6 +69,21 @@ public function getEnumClass(): string
6369
return $this->enumClass;
6470
}
6571

72+
public function addUsage(EnumTypeUsageAsset $usage): self
73+
{
74+
$this->usages[] = $usage;
75+
76+
return $this;
77+
}
78+
79+
/**
80+
* @return EnumTypeUsageAsset[]
81+
*/
82+
public function getUsages(): array
83+
{
84+
return $this->usages;
85+
}
86+
6687
/**
6788
* @param AbstractPlatform $platform
6889
* @throws InvalidArgumentException
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Pfilsx\PostgreSQLDoctrine\DBAL\Schema;
5+
6+
final class EnumTypeUsageAsset
7+
{
8+
private string $table;
9+
10+
private string $column;
11+
12+
private ?string $default;
13+
14+
public function __construct(string $table, string $column, ?string $default = null)
15+
{
16+
$this->table = $table;
17+
$this->column = $column;
18+
$this->default = $default;
19+
}
20+
21+
public function getTable(): string
22+
{
23+
return $this->table;
24+
}
25+
26+
public function getColumn(): string
27+
{
28+
return $this->column;
29+
}
30+
31+
public function getDefault(): ?string
32+
{
33+
return $this->default;
34+
}
35+
}

src/DBAL/Schema/PostgreSQLSchemaManager.php

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -330,17 +330,29 @@ protected function _getPortableTableColumnDefinition($tableColumn): Column
330330

331331
private function getPortableEnumTypesList(array $rawTypes): array
332332
{
333-
$typeGroups = [];
334-
foreach ($rawTypes as $rawType) {
335-
$typeGroup = $typeGroups[$rawType['name']] ?? [];
336-
$typeGroup['comment'] = $rawType['comment'];
337-
$typeGroup['labels'][] = $rawType['label'];
338-
$typeGroups[$rawType['name']] = $typeGroup;
339-
}
340-
341333
$list = [];
342-
foreach ($typeGroups as $name => $typeGroup) {
343-
$list[] = new EnumTypeAsset($name, $typeGroup['comment'], $typeGroup['labels']);
334+
foreach ($rawTypes as $rawType) {
335+
$labels = json_decode($rawType['labels'], true);
336+
usort($labels, static fn (array $a, array $b) => $a['order'] <=> $b['order']);
337+
338+
$usages = json_decode($rawType['usages'], true);
339+
foreach ($usages as &$usage) {
340+
$default = $usage['default'] ?? null;
341+
if ($default !== null) {
342+
$default = trim(explode('::', $default)[0], '\'');
343+
}
344+
$usage['default'] = $default;
345+
}
346+
347+
$list[] = new EnumTypeAsset(
348+
$rawType['name'],
349+
$rawType['comment'],
350+
array_column($labels, 'label'),
351+
array_map(
352+
static fn (array $usage) => new EnumTypeUsageAsset($usage['table'], $usage['column'], $usage['default']),
353+
$usages
354+
)
355+
);
344356
}
345357

346358
return $list;

src/DBAL/Schema/Schema.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class Schema extends BaseSchema
1313
/**
1414
* @var EnumTypeAsset[]
1515
*/
16-
private array $enumTypes = [];
16+
private array $_enumTypes = [];
1717

1818
public function __construct(
1919
array $tables = [],
@@ -25,7 +25,7 @@ public function __construct(
2525
parent::__construct($tables, $sequences, $schemaConfig, $namespaces);
2626

2727
foreach ($types as $type) {
28-
$this->addEnumType($type);
28+
$this->addEnumType($type->getName(), $type);
2929
}
3030
}
3131

@@ -46,7 +46,7 @@ public function createTable($name): Table
4646
*/
4747
public function getEnumTypes(): array
4848
{
49-
return $this->enumTypes;
49+
return $this->_enumTypes;
5050
}
5151

5252
public function getEnumType(string $name): EnumTypeAsset
@@ -56,25 +56,25 @@ public function getEnumType(string $name): EnumTypeAsset
5656
throw new SchemaException("There exists no enum type with the name \"$name\".");
5757
}
5858

59-
return $this->enumTypes[$name];
59+
return $this->_enumTypes[$name];
6060
}
6161

6262
public function hasEnumType(string $name): bool
6363
{
6464
$name = $this->getFullQualifiedAssetName($name);
6565

66-
return isset($this->enumTypes[$name]);
66+
return isset($this->_enumTypes[$name]);
6767
}
6868

69-
public function addEnumType(EnumTypeAsset $type): void
69+
public function addEnumType(string $name, EnumTypeAsset $type): void
7070
{
71-
$typeName = $type->getFullQualifiedName($this->getName());
71+
$typeName = $this->getFullQualifiedAssetName($name);
7272

73-
if (isset($this->enumTypes[$typeName])) {
73+
if (isset($this->_enumTypes[$typeName])) {
7474
throw new SchemaException("The enum type \"$typeName\" already exists.");
7575
}
7676

77-
$this->enumTypes[$typeName] = $type;
77+
$this->_enumTypes[$typeName] = $type;
7878
}
7979

8080
private function getFullQualifiedAssetName($name): string

src/Tools/EnumTool.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static function getEnumTypeNameFromClassName(string $className): string
2929
/**
3030
* @param class-string<EnumInterface|\UnitEnum> $className
3131
* @throws InvalidArgumentException
32-
* @return array<int|string>
32+
* @return string[]
3333
*/
3434
public static function getEnumLabelsByClassName(string $className): array
3535
{

src/Tools/SchemaTool.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Doctrine\ORM\Tools\SchemaTool as BaseTool;
2323
use Doctrine\ORM\Tools\ToolEvents;
2424
use Pfilsx\PostgreSQLDoctrine\DBAL\Schema\EnumTypeAsset;
25+
use Pfilsx\PostgreSQLDoctrine\DBAL\Schema\EnumTypeUsageAsset;
2526
use Pfilsx\PostgreSQLDoctrine\DBAL\Schema\Schema;
2627
use Pfilsx\PostgreSQLDoctrine\DBAL\Schema\Table;
2728
use Pfilsx\PostgreSQLDoctrine\DBAL\Type\EnumType;
@@ -750,10 +751,33 @@ private function gatherEnumTypes(ClassMetadata $metadata, Schema $schema): void
750751
throw new SchemaException('The option "enumType" has to be specified and has to be a real fully qualified class name.');
751752
}
752753

753-
$enumType = EnumTypeAsset::fromEnumClassName($enumTypeClass);
754+
$enumName = EnumTool::getEnumTypeNameFromClassName($enumTypeClass);
755+
756+
$enumType = $schema->hasEnumType($enumName)
757+
? $schema->getEnumType($enumName)
758+
: EnumTypeAsset::fromEnumClassName($enumName, $enumTypeClass)
759+
;
760+
761+
$rawDefault = ($field['options'] ?? [])['default'] ?? null;
762+
763+
if ($rawDefault instanceof \BackedEnum) {
764+
$default = $rawDefault->value;
765+
} elseif ($rawDefault instanceof \UnitEnum) {
766+
$default = $rawDefault->name;
767+
} else {
768+
$default = $rawDefault;
769+
}
770+
771+
$enumType->addUsage(
772+
new EnumTypeUsageAsset(
773+
$metadata->getTableName(),
774+
$field['columnName'],
775+
$default
776+
)
777+
);
754778

755779
if (!$schema->hasEnumType($enumType->getName())) {
756-
$schema->addEnumType($enumType);
780+
$schema->addEnumType($enumName, $enumType);
757781
}
758782
}
759783
}

0 commit comments

Comments
 (0)