Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Phinx/Db/Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ interface AdapterInterface
self::PHINX_TYPE_POLYGON,
];

// only for mysql so far
// MySQL-specific types
public const PHINX_TYPE_MEDIUM_INTEGER = 'mediuminteger';
public const PHINX_TYPE_ENUM = 'enum';
public const PHINX_TYPE_SET = 'set';
public const PHINX_TYPE_YEAR = 'year';

// Supported by MySQL and PostgreSQL
public const PHINX_TYPE_ENUM = 'enum';

// only for postgresql so far
public const PHINX_TYPE_CIDR = 'cidr';
public const PHINX_TYPE_INET = 'inet';
Expand Down
147 changes: 140 additions & 7 deletions src/Phinx/Db/Adapter/PostgresAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class PostgresAdapter extends PdoAdapter
self::PHINX_TYPE_MACADDR,
self::PHINX_TYPE_INTERVAL,
self::PHINX_TYPE_BINARYUUID,
self::PHINX_TYPE_ENUM,
];

private const GIN_INDEX_TYPE = 'gin';
Expand Down Expand Up @@ -277,7 +278,7 @@ public function createTable(Table $table, array $columns = [], array $indexes =

$this->columnsWithComments = [];
foreach ($columns as $column) {
$sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
$sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column, $table->getName());
if ($this->useIdentity && $column->getIdentity() && $column->getGenerated() !== null) {
$sql .= sprintf(' GENERATED %s AS IDENTITY', $column->getGenerated());
}
Expand All @@ -304,6 +305,22 @@ public function createTable(Table $table, array $columns = [], array $indexes =
}

$sql .= ')';

// Emit CREATE TYPE ... AS ENUM statements before the CREATE TABLE statement
foreach ($columns as $column) {
if ($column->getType() === static::PHINX_TYPE_ENUM) {
$values = $column->getValues();
if (!empty($values)) {
$typeName = $this->getEnumTypeName($table->getName(), $column->getName());
$queries[] = sprintf(
'CREATE TYPE %s AS ENUM (%s)',
$this->quoteColumnName($typeName),
implode(', ', array_map(fn($v) => $this->getConnection()->quote($v), $values)),
);
}
}
Comment thread
ErfanMomeniii marked this conversation as resolved.
}

$queries[] = $sql;

// process column comments
Expand Down Expand Up @@ -423,8 +440,20 @@ protected function getDropTableInstructions(string $tableName): AlterInstruction
{
$this->removeCreatedTable($tableName);
$sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
$instructions = new AlterInstructions([], [$sql]);

// Drop any Phinx-managed enum types associated with this table's columns
foreach ($this->getColumns($tableName) as $column) {
$typeName = $this->getEnumTypeName($tableName, $column->getName());
if ($this->hasEnumType($typeName)) {
$instructions->addPostStep(sprintf(
'DROP TYPE %s',
$this->quoteColumnName($typeName),
));
}
}
Comment thread
ErfanMomeniii marked this conversation as resolved.
Outdated

return new AlterInstructions([], [$sql]);
return $instructions;
}

/**
Expand Down Expand Up @@ -462,9 +491,15 @@ public function getColumns(string $tableName): array
$columnsInfo = $this->fetchAll($sql);
foreach ($columnsInfo as $columnInfo) {
$isUserDefined = strtoupper(trim($columnInfo['data_type'])) === 'USER-DEFINED';
$enumValues = null;

if ($isUserDefined) {
$columnType = Literal::from($columnInfo['udt_name']);
$enumValues = $this->getEnumTypeValues($columnInfo['udt_name']);
if ($enumValues !== null) {
$columnType = static::PHINX_TYPE_ENUM;
} else {
$columnType = Literal::from($columnInfo['udt_name']);
}
} else {
$columnType = $this->getPhinxType($columnInfo['data_type']);
}
Expand Down Expand Up @@ -512,6 +547,11 @@ public function getColumns(string $tableName): array
} elseif ($columnType === self::PHINX_TYPE_DECIMAL) {
$column->setPrecision($columnInfo['numeric_precision']);
}

if ($enumValues !== null) {
$column->setValues($enumValues);
}

$columns[] = $column;
}

Expand Down Expand Up @@ -543,11 +583,23 @@ public function hasColumn(string $tableName, string $columnName): bool
*/
protected function getAddColumnInstructions(Table $table, Column $column): AlterInstructions
{
if ($column->getType() === static::PHINX_TYPE_ENUM) {
$values = $column->getValues();
if (!empty($values)) {
$typeName = $this->getEnumTypeName($table->getName(), $column->getName());
$this->execute(sprintf(
'CREATE TYPE %s AS ENUM (%s)',
$this->quoteColumnName($typeName),
implode(', ', array_map(fn($v) => $this->getConnection()->quote($v), $values)),
));
Comment thread
ErfanMomeniii marked this conversation as resolved.
Outdated
}
}
Comment thread
ErfanMomeniii marked this conversation as resolved.

$instructions = new AlterInstructions();
$instructions->addAlter(sprintf(
'ADD %s %s %s',
$this->quoteColumnName($column->getName()),
$this->getColumnSqlDefinition($column),
$this->getColumnSqlDefinition($column, $table->getName()),
$column->isIdentity() && $column->getGenerated() !== null && $this->useIdentity ?
sprintf('GENERATED %s AS IDENTITY', $column->getGenerated()) : '',
));
Expand Down Expand Up @@ -614,7 +666,7 @@ protected function getChangeColumnInstructions(
$sql = sprintf(
'ALTER COLUMN %s TYPE %s',
$quotedColumnName,
$this->getColumnSqlDefinition($newColumn),
$this->getColumnSqlDefinition($newColumn, $tableName),
);
if (in_array($newColumn->getType(), ['smallinteger', 'integer', 'biginteger'], true)) {
$sql .= sprintf(
Expand Down Expand Up @@ -735,7 +787,18 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa
$this->quoteColumnName($columnName),
);

return new AlterInstructions([$alter]);
$instructions = new AlterInstructions([$alter]);

// Drop the associated Phinx-managed enum type if it exists
$typeName = $this->getEnumTypeName($tableName, $columnName);
if ($this->hasEnumType($typeName)) {
$instructions->addPostStep(sprintf(
'DROP TYPE %s',
$this->quoteColumnName($typeName),
));
}

return $instructions;
}

/**
Expand Down Expand Up @@ -1098,6 +1161,8 @@ public function getSqlType(Literal|string $type, ?int $limit = null): array
return ['name' => 'bytea'];
case static::PHINX_TYPE_INTERVAL:
return ['name' => 'interval'];
case static::PHINX_TYPE_ENUM:
return ['name' => 'enum'];
// Geospatial database types
// Spatial storage in Postgres is done via the PostGIS extension,
// which enables the use of the "geography" type in combination
Expand Down Expand Up @@ -1227,9 +1292,10 @@ public function dropDatabase($name): void
* Gets the PostgreSQL Column Definition for a Column object.
*
* @param \Phinx\Db\Table\Column $column Column
* @param string|null $tableName Table name, used to derive the enum type name when column type is 'enum'
* @return string
*/
protected function getColumnSqlDefinition(Column $column): string
protected function getColumnSqlDefinition(Column $column, ?string $tableName = null): string
{
$buffer = [];

Expand All @@ -1241,6 +1307,11 @@ protected function getColumnSqlDefinition(Column $column): string
} else {
$buffer[] = 'SERIAL';
}
} elseif ($column->getType() === static::PHINX_TYPE_ENUM) {
$typeName = $tableName !== null
? $this->getEnumTypeName($tableName, $column->getName())
: $column->getName();
$buffer[] = $this->quoteColumnName($typeName);
} elseif ($column->getType() instanceof Literal) {
$buffer[] = (string)$column->getType();
} else {
Expand Down Expand Up @@ -1295,6 +1366,68 @@ protected function getColumnSqlDefinition(Column $column): string
return implode(' ', $buffer);
}

/**
* Returns the Phinx-managed PostgreSQL enum type name for a given table column.
*
* The convention is `{table}_{column}` using the unqualified table name so that
* two tables cannot accidentally share the same type with different values.
*
* @param string $tableName Table name (may be schema-qualified)
* @param string $columnName Column name
* @return string
*/
protected function getEnumTypeName(string $tableName, string $columnName): string
{
return $this->getSchemaName($tableName)['table'] . '_' . $columnName;
}

/**
* Returns the enum label values for a given PostgreSQL enum type name, or null if the
* type does not exist or is not an enum.
*
* @param string $typeName Type name (unqualified)
* @return string[]|null
*/
protected function getEnumTypeValues(string $typeName): ?array
{
$sql = sprintf(
"SELECT e.enumlabel
FROM pg_type t
JOIN pg_enum e ON t.oid = e.enumtypid
JOIN pg_namespace n ON t.typnamespace = n.oid
WHERE t.typname = %s AND t.typtype = 'e' AND n.nspname = %s
ORDER BY e.enumsortorder",
$this->getConnection()->quote($typeName),
$this->getConnection()->quote($this->schema),
);
Comment thread
ErfanMomeniii marked this conversation as resolved.
$rows = $this->fetchAll($sql);

return !empty($rows) ? array_column($rows, 'enumlabel') : null;
}

/**
* Returns true if a PostgreSQL enum type with the given unqualified name exists in the
* current schema.
*
* @param string $typeName Type name (unqualified)
* @return bool
*/
protected function hasEnumType(string $typeName): bool
{
$sql = sprintf(
"SELECT EXISTS(
SELECT 1 FROM pg_type t
JOIN pg_namespace n ON t.typnamespace = n.oid
WHERE t.typname = %s AND t.typtype = 'e' AND n.nspname = %s
) AS type_exists",
$this->getConnection()->quote($typeName),
$this->getConnection()->quote($this->schema),
);
$result = $this->fetchRow($sql);

return (bool)$result['type_exists'];
}

/**
* Gets the PostgreSQL Column Comment Definition for a column object.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Phinx/Db/Table/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ class Column
/** MySQL-only column type */
public const MEDIUMINTEGER = AdapterInterface::PHINX_TYPE_MEDIUM_INTEGER;
/** MySQL-only column type */
public const ENUM = AdapterInterface::PHINX_TYPE_ENUM;
/** MySQL-only column type */
public const SET = AdapterInterface::PHINX_TYPE_STRING;
/** MySQL-only column type */
public const BLOB = AdapterInterface::PHINX_TYPE_BLOB;
/** MySQL-only column type */
public const YEAR = AdapterInterface::PHINX_TYPE_YEAR;
/** MySQL/Postgres-only column type */
public const ENUM = AdapterInterface::PHINX_TYPE_ENUM;
/** MySQL/Postgres-only column type */
public const JSON = AdapterInterface::PHINX_TYPE_JSON;
/** Postgres-only column type */
public const JSONB = AdapterInterface::PHINX_TYPE_JSONB;
Expand Down
Loading