diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a0e4eac..32a9e6579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ - New #307: Add range and multirange columns support (@vjik, @Gerych1984) - Enh #464: Load column's check expressions for table schema (@Tigrov) - Bug #467: Fix column definition parsing in cases with parentheses (@vjik) +- New #465: 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 26bb1c45d..1f38e8c60 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -122,6 +122,7 @@ protected function getDbType(ColumnInterface $column): string ColumnType::DATE => 'date', ColumnType::STRUCTURED => 'jsonb', ColumnType::JSON => 'jsonb', + ColumnType::ENUM => 'varchar', default => 'varchar', }, 'timestamp without time zone' => 'timestamp', diff --git a/src/Schema.php b/src/Schema.php index da557440f..96e678caa 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -44,7 +44,7 @@ * column_default: string|null, * is_autoinc: bool|string, * sequence_name: string|null, - * enum_values: string|null, + * values: string|null, * size: int|string|null, * scale: int|string|null, * contype: string|null, @@ -292,7 +292,7 @@ protected function findColumns(TableSchemaInterface $table): bool )::varchar[], ',') ELSE NULL - END AS enum_values, + END AS values, COALESCE( information_schema._pg_char_max_length( COALESCE(td.oid, tb.oid, a.atttypid), @@ -467,7 +467,7 @@ protected function loadResultColumn(array $metadata): ?ColumnInterface )::varchar[], ',') ELSE NULL - END AS enum_values + END AS values FROM pg_type AS t LEFT JOIN pg_type AS t2 ON t.typcategory='A' AND t2.oid = t.typelem OR t.typbasetype > 0 AND t2.oid = t.typbasetype LEFT JOIN pg_namespace AS ns ON ns.oid = COALESCE(t2.typnamespace, t.typnamespace) @@ -486,8 +486,8 @@ protected function loadResultColumn(array $metadata): ?ColumnInterface } $columnInfo['type'] = ColumnType::STRUCTURED; - } elseif (!empty($typeInfo['enum_values'])) { - $columnInfo['enumValues'] = explode(',', str_replace(["''"], ["'"], $typeInfo['enum_values'])); + } elseif (!empty($typeInfo['values'])) { + $columnInfo['values'] = explode(',', str_replace(["''"], ["'"], $typeInfo['values'])); } } @@ -525,9 +525,9 @@ private function loadColumn(array $info): ColumnInterface 'collation' => $collation, 'comment' => $info['column_comment'], 'dbType' => $dbType, - 'enumValues' => $info['enum_values'] !== null - ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) - : null, + 'values' => $info['values'] !== null + ? explode(',', str_replace(["''"], ["'"], $info['values'])) + : $this->tryGetEnumValuesFromCheck($info['check']), 'name' => $info['column_name'], 'notNull' => !$info['is_nullable'], 'primaryKey' => $info['contype'] === 'p', @@ -679,4 +679,31 @@ private function loadTableConstraints(string $tableName, string $returnType): ar return $result[$returnType]; } + + /** + * @psalm-return list|null + */ + private function tryGetEnumValuesFromCheck(?string $check): ?array + { + if ($check === null) { + return null; + } + + preg_match_all( + "~ANY\s*\(\(ARRAY\[('(?:''|[^'])*'[^,\]]*)(?:,\s*(?1))*~", + $check, + $block, + ); + + if (empty($block[0][0])) { + return null; + } + + preg_match_all("~'((?:''|[^'])*)'~", $block[0][0], $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 000000000..cabe38ac8 --- /dev/null +++ b/tests/Column/EnumColumnTest.php @@ -0,0 +1,62 @@ +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 [ + <<