Skip to content

Commit 94a8405

Browse files
authored
Add ENUM column (#465)
1 parent 9f0ec01 commit 94a8405

4 files changed

Lines changed: 99 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
- New #307: Add range and multirange columns support (@vjik, @Gerych1984)
6161
- Enh #464: Load column's check expressions for table schema (@Tigrov)
6262
- Bug #467: Fix column definition parsing in cases with parentheses (@vjik)
63+
- New #465: Add enumeration column type support (@vjik)
6364

6465
## 1.3.0 March 21, 2024
6566

src/Column/ColumnDefinitionBuilder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ protected function getDbType(ColumnInterface $column): string
122122
ColumnType::DATE => 'date',
123123
ColumnType::STRUCTURED => 'jsonb',
124124
ColumnType::JSON => 'jsonb',
125+
ColumnType::ENUM => 'varchar',
125126
default => 'varchar',
126127
},
127128
'timestamp without time zone' => 'timestamp',

src/Schema.php

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
* column_default: string|null,
4545
* is_autoinc: bool|string,
4646
* sequence_name: string|null,
47-
* enum_values: string|null,
47+
* values: string|null,
4848
* size: int|string|null,
4949
* scale: int|string|null,
5050
* contype: string|null,
@@ -292,7 +292,7 @@ protected function findColumns(TableSchemaInterface $table): bool
292292
)::varchar[],
293293
',')
294294
ELSE NULL
295-
END AS enum_values,
295+
END AS values,
296296
COALESCE(
297297
information_schema._pg_char_max_length(
298298
COALESCE(td.oid, tb.oid, a.atttypid),
@@ -467,7 +467,7 @@ protected function loadResultColumn(array $metadata): ?ColumnInterface
467467
)::varchar[],
468468
',')
469469
ELSE NULL
470-
END AS enum_values
470+
END AS values
471471
FROM pg_type AS t
472472
LEFT JOIN pg_type AS t2 ON t.typcategory='A' AND t2.oid = t.typelem OR t.typbasetype > 0 AND t2.oid = t.typbasetype
473473
LEFT JOIN pg_namespace AS ns ON ns.oid = COALESCE(t2.typnamespace, t.typnamespace)
@@ -486,8 +486,8 @@ protected function loadResultColumn(array $metadata): ?ColumnInterface
486486
}
487487

488488
$columnInfo['type'] = ColumnType::STRUCTURED;
489-
} elseif (!empty($typeInfo['enum_values'])) {
490-
$columnInfo['enumValues'] = explode(',', str_replace(["''"], ["'"], $typeInfo['enum_values']));
489+
} elseif (!empty($typeInfo['values'])) {
490+
$columnInfo['values'] = explode(',', str_replace(["''"], ["'"], $typeInfo['values']));
491491
}
492492
}
493493

@@ -525,9 +525,9 @@ private function loadColumn(array $info): ColumnInterface
525525
'collation' => $collation,
526526
'comment' => $info['column_comment'],
527527
'dbType' => $dbType,
528-
'enumValues' => $info['enum_values'] !== null
529-
? explode(',', str_replace(["''"], ["'"], $info['enum_values']))
530-
: null,
528+
'values' => $info['values'] !== null
529+
? explode(',', str_replace(["''"], ["'"], $info['values']))
530+
: $this->tryGetEnumValuesFromCheck($info['check']),
531531
'name' => $info['column_name'],
532532
'notNull' => !$info['is_nullable'],
533533
'primaryKey' => $info['contype'] === 'p',
@@ -679,4 +679,31 @@ private function loadTableConstraints(string $tableName, string $returnType): ar
679679

680680
return $result[$returnType];
681681
}
682+
683+
/**
684+
* @psalm-return list<string>|null
685+
*/
686+
private function tryGetEnumValuesFromCheck(?string $check): ?array
687+
{
688+
if ($check === null) {
689+
return null;
690+
}
691+
692+
preg_match_all(
693+
"~ANY\s*\(\(ARRAY\[('(?:''|[^'])*'[^,\]]*)(?:,\s*(?1))*~",
694+
$check,
695+
$block,
696+
);
697+
698+
if (empty($block[0][0])) {
699+
return null;
700+
}
701+
702+
preg_match_all("~'((?:''|[^'])*)'~", $block[0][0], $matches);
703+
704+
return array_map(
705+
static fn($v) => str_replace("''", "'", $v),
706+
$matches[1] ?? [],
707+
);
708+
}
682709
}

tests/Column/EnumColumnTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Pgsql\Tests\Column;
6+
7+
use PHPUnit\Framework\Attributes\TestWith;
8+
use Yiisoft\Db\Pgsql\Tests\Support\IntegrationTestTrait;
9+
use Yiisoft\Db\Schema\Column\EnumColumn;
10+
use Yiisoft\Db\Tests\Common\CommonEnumColumnTest;
11+
12+
final class EnumColumnTest extends CommonEnumColumnTest
13+
{
14+
use IntegrationTestTrait;
15+
16+
#[TestWith(['INTEGER CHECK (status IN (1, 2, 3))'])]
17+
#[TestWith(["TEXT CHECK (status != 'abc')"])]
18+
#[TestWith(["TEXT CHECK (status IN ('a', 'b') OR status = 'x')"])]
19+
#[TestWith(["TEXT CHECK (status IN ('a', 'b') OR status IN ('x', 'y'))"])]
20+
public function testNonEnumCheck(string $columnDefinition): void
21+
{
22+
$this->dropTable('test_enum_table');
23+
$this->executeStatements(
24+
<<<SQL
25+
CREATE TABLE test_enum_table (
26+
id INTEGER,
27+
status $columnDefinition
28+
)
29+
SQL,
30+
);
31+
32+
$db = $this->getSharedConnection();
33+
$column = $db->getTableSchema('test_enum_table')->getColumn('status');
34+
35+
$this->assertNotInstanceOf(EnumColumn::class, $column);
36+
37+
$this->dropTable('test_enum_table');
38+
}
39+
40+
protected function createDatabaseObjectsStatements(): array
41+
{
42+
return [
43+
<<<SQL
44+
CREATE TYPE enum_status AS ENUM ('active', 'unactive', 'pending')
45+
SQL,
46+
<<<SQL
47+
CREATE TABLE tbl_enum (
48+
id INTEGER,
49+
status enum_status
50+
)
51+
SQL,
52+
];
53+
}
54+
55+
protected function dropDatabaseObjectsStatements(): array
56+
{
57+
return [
58+
'DROP TABLE IF EXISTS tbl_enum',
59+
'DROP TYPE IF EXISTS enum_status',
60+
];
61+
}
62+
}

0 commit comments

Comments
 (0)