From f5861f8df18faf884f2e79f78fe799307cc15726 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 26 Nov 2025 22:16:49 +0300 Subject: [PATCH 01/11] Add `ENUM` column --- src/Column/ColumnDefinitionBuilder.php | 27 +++++------ src/Schema.php | 28 +++++++++++ tests/Column/EnumColumnTest.php | 66 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 tests/Column/EnumColumnTest.php diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index a355bdd..699aade 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -11,6 +11,7 @@ use Yiisoft\Db\Schema\Column\ColumnInterface; use function ceil; +use function in_array; use function log10; use function strtoupper; @@ -61,23 +62,20 @@ protected function buildCheck(ColumnInterface $column): string if (empty($check)) { $name = $column->getName(); - - if (empty($name)) { - return ''; + if (!empty($name)) { + $type = $column->getType(); + if (in_array($type, [ColumnType::ARRAY, ColumnType::STRUCTURED, ColumnType::JSON], true)) { + return version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '<') + ? ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IS JSON)' + : ''; + } + if ($type === ColumnType::BOOLEAN) { + return ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IN (0,1))'; + } } - - return match ($column->getType()) { - ColumnType::ARRAY, ColumnType::STRUCTURED, ColumnType::JSON - => version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '<') - ? ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IS JSON)' - : '', - ColumnType::BOOLEAN - => ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IN (0,1))', - default => '', - }; } - return " CHECK ($check)"; + return parent::buildCheck($column); } protected function buildOnDelete(string $onDelete): string @@ -133,6 +131,7 @@ protected function getDbType(ColumnInterface $column): string => version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '>=') ? 'json' : 'clob', + ColumnType::ENUM => 'varchar2(' . ($size ?? 255) . ')', default => 'varchar2', }, 'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone', diff --git a/src/Schema.php b/src/Schema.php index 5cf3ea6..47930f4 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -457,6 +457,7 @@ private function loadColumn(array $info): ColumnInterface 'size' => $info['size'] !== null ? (int) $info['size'] : null, 'table' => $info['table'], 'unique' => $info['constraint_type'] === 'U', + 'values' => $this->tryGetEnumValuesFromCheck($info['column_name'], $info['check']), ]; if ($dbType === 'timestamp with local time zone') { @@ -544,4 +545,31 @@ private function loadTableConstraints(string $tableName, string $returnType): ar return $result[$returnType]; } + + /** + * @psalm-return list|null + */ + private function tryGetEnumValuesFromCheck(string $columnName, ?string $check): ?array + { + if ($check === null) { + return null; + } + + $quotedColumnName = preg_quote($columnName, '~'); + if (!preg_match( + "~(?:\"$quotedColumnName\"|$quotedColumnName)\s+IN\s*\((.+)\)~i", + $check, + $block + )) { + return null; + } + + // Выбираем все строки в одинарных кавычках внутри + preg_match_all("~'((?:''|[^'])*)'~", $block[1], $matches); + + return array_map( + static fn($v) => str_replace("''", "'", $v), + $matches[1] + ); + } } diff --git a/tests/Column/EnumColumnTest.php b/tests/Column/EnumColumnTest.php new file mode 100644 index 0000000..d749597 --- /dev/null +++ b/tests/Column/EnumColumnTest.php @@ -0,0 +1,66 @@ +dropTable('test_enum_table'); + $this->executeStatements( + <<getSharedConnection(); + $column = $db->getTableSchema('test_enum_table')->getColumn('status'); + + $this->assertNotInstanceOf(EnumColumn::class, $column); + + $this->dropTable('test_enum_table'); + } + + protected function createDatabaseObjectsStatements(): array + { + return [ + << Date: Wed, 26 Nov 2025 19:19:23 +0000 Subject: [PATCH 02/11] Apply PHP CS Fixer and Rector changes (CI) --- src/Schema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 47930f4..33b690d 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -559,7 +559,7 @@ private function tryGetEnumValuesFromCheck(string $columnName, ?string $check): if (!preg_match( "~(?:\"$quotedColumnName\"|$quotedColumnName)\s+IN\s*\((.+)\)~i", $check, - $block + $block, )) { return null; } @@ -569,7 +569,7 @@ private function tryGetEnumValuesFromCheck(string $columnName, ?string $check): return array_map( static fn($v) => str_replace("''", "'", $v), - $matches[1] + $matches[1], ); } } From abcd56c2aafbbef54962960d77550c7ca6013d4e Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 28 Nov 2025 08:45:39 +0300 Subject: [PATCH 03/11] fix --- tests/Support/IntegrationTestTrait.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Support/IntegrationTestTrait.php b/tests/Support/IntegrationTestTrait.php index 79ae31f..8fd1b1d 100644 --- a/tests/Support/IntegrationTestTrait.php +++ b/tests/Support/IntegrationTestTrait.php @@ -41,6 +41,23 @@ protected function parseDump(string $content): array ); } + protected function dropTable(string $table): void + { + $db = TestConnection::getShared(); + $table = $db->getQuoter()->quoteTableName($table); + $sql = <<createCommand($sql)->execute(); + } + protected function dropView(ConnectionInterface $db, string $view): void { $view = $db->getQuoter()->quoteTableName($view); From 83bd9b872b8d18586e17f43c711cb20a0d616c82 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 28 Nov 2025 11:25:28 +0300 Subject: [PATCH 04/11] improve --- src/Column/ColumnDefinitionBuilder.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index 699aade..d497274 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -4,12 +4,15 @@ namespace Yiisoft\Db\Oracle\Column; +use _PHPStan_e870ac104\Symfony\Component\Console\Exception\LogicException; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\ReferentialAction; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\QueryBuilder\AbstractColumnDefinitionBuilder; use Yiisoft\Db\Schema\Column\ColumnInterface; +use Yiisoft\Db\Schema\Column\EnumColumn; + use function ceil; use function in_array; use function log10; @@ -131,7 +134,7 @@ protected function getDbType(ColumnInterface $column): string => version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '>=') ? 'json' : 'clob', - ColumnType::ENUM => 'varchar2(' . ($size ?? 255) . ')', + ColumnType::ENUM => 'varchar2(' . $this->calcEnumSize($column) . ')', default => 'varchar2', }, 'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone', @@ -145,4 +148,23 @@ protected function getDefaultUuidExpression(): string { return 'sys_guid()'; } + + private function calcEnumSize(ColumnInterface $column): int + { + $size = $column->getSize(); + if ($size !== null) { + return $size; + } + + if ($column instanceof EnumColumn) { + return max( + array_map( + strlen(...), + $column->getValues(), + ), + ); + } + + throw new LogicException('Cannot calculate enum size. Set the size explicitly or use `EnumColumn` instance.'); + } } From 5d18062f0cf4e520fd84d39b1a77df39e4ce350b Mon Sep 17 00:00:00 2001 From: vjik <525501+vjik@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:26:08 +0000 Subject: [PATCH 05/11] Apply PHP CS Fixer and Rector changes (CI) --- src/Column/ColumnDefinitionBuilder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index d497274..42a168a 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -10,7 +10,6 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\QueryBuilder\AbstractColumnDefinitionBuilder; use Yiisoft\Db\Schema\Column\ColumnInterface; - use Yiisoft\Db\Schema\Column\EnumColumn; use function ceil; From 4e8128e49824e79eb30aea7a44ee699ab0535445 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 29 Nov 2025 09:58:10 +0300 Subject: [PATCH 06/11] fix --- src/Column/ColumnDefinitionBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index 42a168a..037aa19 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -4,7 +4,7 @@ namespace Yiisoft\Db\Oracle\Column; -use _PHPStan_e870ac104\Symfony\Component\Console\Exception\LogicException; +use LogicException; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\ReferentialAction; use Yiisoft\Db\Exception\NotSupportedException; From eb2eb58e7e2f4f74d4156fd093d99900d85c363b Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 29 Nov 2025 11:51:41 +0300 Subject: [PATCH 07/11] Update src/Column/ColumnDefinitionBuilder.php Co-authored-by: Sergei Tigrov --- src/Column/ColumnDefinitionBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index 037aa19..e0be90f 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -133,7 +133,7 @@ protected function getDbType(ColumnInterface $column): string => version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '>=') ? 'json' : 'clob', - ColumnType::ENUM => 'varchar2(' . $this->calcEnumSize($column) . ')', + ColumnType::ENUM => 'varchar2(' . $this->calcEnumSize($column) . ' BYTE)', default => 'varchar2', }, 'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone', From d790e65d34575198fea584c428fbfd7c6372ddb7 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 29 Nov 2025 12:44:57 +0300 Subject: [PATCH 08/11] improve --- src/Schema.php | 2 +- tests/Column/EnumColumnTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index 33b690d..69015d0 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -557,7 +557,7 @@ private function tryGetEnumValuesFromCheck(string $columnName, ?string $check): $quotedColumnName = preg_quote($columnName, '~'); if (!preg_match( - "~(?:\"$quotedColumnName\"|$quotedColumnName)\s+IN\s*\((.+)\)~i", + "~^\s*(?:\"$quotedColumnName\"|$quotedColumnName)\s+IN\s*\(\s*(('(?:''|[^'])*')(?:,\s*(?2))*)\s*\)\s*$~i", $check, $block, )) { diff --git a/tests/Column/EnumColumnTest.php b/tests/Column/EnumColumnTest.php index d749597..4ad587f 100644 --- a/tests/Column/EnumColumnTest.php +++ b/tests/Column/EnumColumnTest.php @@ -16,6 +16,8 @@ final class EnumColumnTest extends CommonEnumColumnTest #[TestWith(['INTEGER CHECK ("status" IN (1, 2, 3))'])] #[TestWith(["VARCHAR2(10) CHECK (\"status\" != 'abc')"])] #[TestWith(["VARCHAR2(10) CHECK (\"status\" NOT IN ('a', 'b', 'c'))"])] + #[TestWith(["VARCHAR2(10) CHECK (\"status\" IN ('a', 'b', 'c') OR \"status\" = 'x')"])] + #[TestWith(["VARCHAR2(10) CHECK (\"status\" IN ('a', 'b', 'c') OR \"status\" IN ('x', 'y', 'z'))"])] public function testNonEnumCheck(string $columnDefinition): void { $this->dropTable('test_enum_table'); From b39c413f0812e0493e90fff6557090702465a67b Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 29 Nov 2025 13:29:02 +0300 Subject: [PATCH 09/11] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 204d17f..f14b44a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ - Enh #373: Adapt to `DQLQueryBuilderInterface::buildWithQueries()` signature changes in `yiisoft/db` package (@vjik) - Chg #378: Throw exception on "unsigned" column usage (@vjik) - Bug #383: Fix column definition parsing in cases with parentheses (@vjik) +- New #382: Add enumeration column type support (@vjik) ## 1.3.0 March 21, 2024 From 2c83322edabe460fe0f39865c7322d05a5f865a7 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 29 Nov 2025 18:04:31 +0300 Subject: [PATCH 10/11] fix --- tests/Column/EnumColumnTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Column/EnumColumnTest.php b/tests/Column/EnumColumnTest.php index 4ad587f..cbd869c 100644 --- a/tests/Column/EnumColumnTest.php +++ b/tests/Column/EnumColumnTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Column; +namespace Yiisoft\Db\Oracle\Tests\Column; use PHPUnit\Framework\Attributes\TestWith; use Yiisoft\Db\Oracle\Tests\Support\IntegrationTestTrait; From d1fddb47924077aa267a54b8e36574445c35c6b3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Sat, 29 Nov 2025 18:06:18 +0300 Subject: [PATCH 11/11] cleanup --- src/Schema.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index 69015d0..f3d85cf 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -564,7 +564,6 @@ private function tryGetEnumValuesFromCheck(string $columnName, ?string $check): return null; } - // Выбираем все строки в одинарных кавычках внутри preg_match_all("~'((?:''|[^'])*)'~", $block[1], $matches); return array_map(