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 diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index a355bdd..e0be90f 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -4,13 +4,16 @@ namespace Yiisoft\Db\Oracle\Column; +use 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; use function strtoupper; @@ -61,23 +64,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 +133,7 @@ protected function getDbType(ColumnInterface $column): string => version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '>=') ? 'json' : 'clob', + ColumnType::ENUM => 'varchar2(' . $this->calcEnumSize($column) . ' BYTE)', default => 'varchar2', }, 'timestamp with time zone' => 'timestamp' . ($size !== null ? "($size)" : '') . ' with time zone', @@ -146,4 +147,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.'); + } } diff --git a/src/Schema.php b/src/Schema.php index 5cf3ea6..f3d85cf 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,30 @@ 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( + "~^\s*(?:\"$quotedColumnName\"|$quotedColumnName)\s+IN\s*\(\s*(('(?:''|[^'])*')(?:,\s*(?2))*)\s*\)\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..cbd869c --- /dev/null +++ b/tests/Column/EnumColumnTest.php @@ -0,0 +1,68 @@ +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 [ + <<getQuoter()->quoteTableName($table); + $sql = <<createCommand($sql)->execute(); + } + protected function dropView(ConnectionInterface $db, string $view): void { $view = $db->getQuoter()->quoteTableName($view);