Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions src/db/ColumnSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ class ColumnSchema extends \yii\db\ColumnSchema
* ```
*/
public $xDbType;

/**
* @var string|null
* Custom Enum type
* @see \cebe\yii2openapi\lib\items\Attribute::$xDbType and `x-db-type` docs in README.md // TODO SK
*/
public $xEnumType;
}
5 changes: 4 additions & 1 deletion src/lib/AttributeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ protected function resolveProperty(
if ($property->isVirtual()) {
throw new InvalidDefinitionException('References not supported for virtual attributes');
}

if ($property->isNonDbReference()) {
$attribute->asNonDbReference($property->getRefClassName());
$relation = Yii::createObject(
Expand Down Expand Up @@ -288,6 +288,9 @@ protected function resolveProperty(
if ($property->hasEnum()) {
$attribute->setEnumValues($property->getAttr('enum'));
}
if ($property->hasAttr('x-enum-type')) {
$attribute->setXEnumType($property->getAttr('x-enum-type'));
}
}

if ($property->hasRefItems()) {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/ColumnToCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public function getAlterExpression(bool $addUsingExpression = false):string
{
if ($this->isEnum() && ApiGenerator::isPostgres()) {
$rawTableName = $this->dbSchema->getRawTableName($this->tableAlias);
$enumTypeName = 'enum_'.$rawTableName.'_'.$this->column->name;
$enumTypeName = $this->column->xEnumType ?? 'enum_'.$rawTableName.'_'.$this->column->name;
return "'" . sprintf('"'.$enumTypeName.'" USING "%1$s"::"'.$enumTypeName.'"', $this->column->name) . "'";
}
if ($this->column->dbType === 'tsvector') {
Expand Down Expand Up @@ -399,7 +399,7 @@ private function resolveEnumType():void
{
if (ApiGenerator::isPostgres()) {
$rawTableName = $this->dbSchema->getRawTableName($this->tableAlias);
$this->rawParts['type'] = '"enum_'.$rawTableName.'_' . $this->column->name.'"';
$this->rawParts['type'] = $this->column->xEnumType ?? '"enum_'.$rawTableName.'_' . $this->column->name.'"';
return;
}
$this->rawParts['type'] = 'enum(' . self::mysqlEnumToString($this->column->enumValues) . ')';
Expand Down
15 changes: 15 additions & 0 deletions src/lib/items/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ class Attribute extends BaseObject
*/
public $defaultValue;

/**
* Custom enum type naming
* string | null
*/
public $xEnumType;

/**
* @var array|null
*/
Expand Down Expand Up @@ -200,6 +206,12 @@ public function setDefault($value):Attribute
return $this;
}

public function setXEnumType(string $xEnumType):Attribute
{
$this->xEnumType = $xEnumType;
return $this;
}

public function setEnumValues(array $values):Attribute
{
$this->enumValues = $values;
Expand Down Expand Up @@ -330,6 +342,9 @@ public function toColumnSchema():ColumnSchema
//@TODO: Need to discuss
$column->defaultValue = null;
}
if (!empty($this->xEnumType)) {
$column->xEnumType = $this->xEnumType;
}
if (is_array($this->enumValues)) {
$column->enumValues = $this->enumValues;
}
Expand Down
37 changes: 27 additions & 10 deletions src/lib/migrations/BaseMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,13 @@ protected function isNeedUsingExpression(string $fromDbType, string $toDbType):b
public function tmpSaveNewCol(string $tableAlias, \cebe\yii2openapi\db\ColumnSchema $columnSchema): \yii\db\ColumnSchema
{
$tmpTableName = 'tmp_table_';
$tmpEnumName = function (string $columnName): string {
return '"tmp_enum_'.$columnName.'_"';

$tmpEnumName = function (\cebe\yii2openapi\db\ColumnSchema $columnSchema): string {
if ($columnSchema->xEnumType) {
// return 'tmp_'.$columnSchema->xEnumType;
return $columnSchema->xEnumType;
}
return '"tmp_enum_'.$columnSchema->name.'_"';
};
$rawTableName = $this->db->schema->getRawTableName($tableAlias);
$innerEnumTypeName = "\"enum_{$tmpTableName}_{$columnSchema->name}\"";
Expand All @@ -432,12 +437,12 @@ public function tmpSaveNewCol(string $tableAlias, \cebe\yii2openapi\db\ColumnSch
$name = MigrationRecordBuilder::quote($columnSchema->name);
$column = [$name.' '.$this->newColStr($tmpTableName, $columnSchema)];
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
$column = strtr($column, [$innerEnumTypeName => $tmpEnumName($columnSchema->name)]);
$column = strtr($column, [$innerEnumTypeName => $tmpEnumName($columnSchema)]);
}
} else {
$column = [$columnSchema->name => $this->newColStr($tmpTableName, $columnSchema)];
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
$column[$columnSchema->name] = strtr($column[$columnSchema->name], [$innerEnumTypeName => $tmpEnumName($columnSchema->name)]);
$column[$columnSchema->name] = strtr($column[$columnSchema->name], [$innerEnumTypeName => $tmpEnumName($columnSchema)]);
}
}

Expand All @@ -448,7 +453,7 @@ public function tmpSaveNewCol(string $tableAlias, \cebe\yii2openapi\db\ColumnSch
return "'$aValue'";
}, $allEnumValues);
Yii::$app->db->createCommand(
'CREATE TYPE '.$tmpEnumName($columnSchema->name).' AS ENUM('.implode(', ', $allEnumValues).')'
'CREATE TYPE '.$tmpEnumName($columnSchema).' AS ENUM('.implode(', ', $allEnumValues).')'
)->execute();
}

Expand All @@ -459,13 +464,25 @@ public function tmpSaveNewCol(string $tableAlias, \cebe\yii2openapi\db\ColumnSch
Yii::$app->db->createCommand()->dropTable($tmpTableName)->execute();

if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {// drop enum
Yii::$app->db->createCommand('DROP TYPE '.$tmpEnumName($columnSchema->name))->execute();
if ('"'.$table->columns[$columnSchema->name]->dbType.'"' !== $tmpEnumName($columnSchema->name)) {
throw new \Exception('Unknown error related to PgSQL enum '.$table->columns[$columnSchema->name]->dbType);
}
Yii::$app->db->createCommand('DROP TYPE '.$tmpEnumName($columnSchema))->execute();

// $table->columns[$columnSchema->name]->dbType = $tmpEnumName($columnSchema);
$table->columns[$columnSchema->name]->dbType = $columnSchema->xEnumType ?? "enum_{$rawTableName}_{$columnSchema->name}";


// if ('"'.$table->columns[$columnSchema->name]->dbType.'"' !== $tmpEnumName($columnSchema)) {
// throw new \Exception('Unknown error related to PgSQL enum '.$table->columns[$columnSchema->name]->dbType);
// }
// reset back column enum name to original as we are comparing with current
// e.g. we get different enum type name such as `enum_status` and `tmp_enum_status_` even there is no change, so below statement fix this issue
$table->columns[$columnSchema->name]->dbType = 'enum_'.$rawTableName.'_'.$columnSchema->name;
// $table->columns[$columnSchema->name]->dbType = $columnSchema->xEnumType ?? 'enum_'.$rawTableName.'_'.$columnSchema->name;
// $table->columns[$columnSchema->name]->dbType = 'enum_'.$rawTableName.'_'.$columnSchema->name;


// if (is_array($desired->enumValues)) {
// $desired->dbType = $columnSchema->xEnumType ?? 'enum_'.$rawTableName.'_'.$desired->name
// }

}

return $table->columns[$columnSchema->name];
Expand Down
18 changes: 14 additions & 4 deletions src/lib/migrations/MigrationRecordBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ final class MigrationRecordBuilder
public const ADD_UNIQUE = MigrationRecordBuilder::INDENT . "\$this->createIndex('%s', '%s', %s, true);";
public const ADD_INDEX = MigrationRecordBuilder::INDENT . "\$this->createIndex('%s', '%s', %s, %s);";
public const DROP_COLUMN = MigrationRecordBuilder::INDENT . "\$this->dropColumn('%s', '%s');";
public const ADD_ENUM = MigrationRecordBuilder::INDENT . "\$this->execute('CREATE TYPE \"enum_%s_%s\" AS ENUM(%s)');";
public const DROP_ENUM = MigrationRecordBuilder::INDENT . "\$this->execute('DROP TYPE \"enum_%s_%s\"');";
public const ADD_ENUM = MigrationRecordBuilder::INDENT . "\$this->execute('CREATE TYPE \"%s\" AS ENUM(%s)');";
public const DROP_ENUM = MigrationRecordBuilder::INDENT . "\$this->execute('DROP TYPE \"%s\"');";
public const DROP_TABLE = MigrationRecordBuilder::INDENT . "\$this->dropTable('%s');";

public const ADD_FK = MigrationRecordBuilder::INDENT . "\$this->addForeignKey('%s', '%s', '%s', '%s', '%s');";
Expand Down Expand Up @@ -203,10 +203,20 @@ public function dropColumnNotNull(string $tableAlias, ColumnSchema $column):stri
return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, '"DROP NOT NULL"');
}

public function createEnum(string $tableAlias, string $columnName, array $values):string
public function createEnum(string $tableAlias, string $columnName, array $values, ?string $enumType = null): string
{
$rawTableName = $this->dbSchema->getRawTableName($tableAlias);
return sprintf(self::ADD_ENUM, $rawTableName, $columnName, ColumnToCode::enumToString($values));

if (is_string($enumType)) {
$enumType = trim($enumType);
}

// -- Decide the final enum type name
// If a custom name is provided, use it verbatim (trim extra quotes).
// Otherwise derive the legacy name "enum_<table>_<column>" (old behavior).
$typeName = $enumType ?: sprintf('enum_%s_%s', $rawTableName, $columnName);

return sprintf(self::ADD_ENUM, $typeName, ColumnToCode::enumToString($values));
}

public function addFk(string $fkName, string $tableAlias, string $fkCol, string $refTable, string $refCol, ?string $onDelete = null, ?string $onUpdate = null):string
Expand Down
43 changes: 38 additions & 5 deletions src/lib/migrations/PostgresMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected function buildColumnsCreation(array $columns):void
foreach ($columns as $column) {
$tableName = $this->model->getTableAlias();
if (static::isEnum($column)) {
$this->migration->addUpCode($this->recordBuilder->createEnum($tableName, $column->name, $column->enumValues))
$this->migration->addUpCode($this->recordBuilder->createEnum($tableName, $column->name, $column->enumValues, $column->xEnumType))
->addDownCode($this->recordBuilder->dropEnum($tableName, $column->name), true);
}
$this->migration->addUpCode($this->recordBuilder->addColumn($tableName, $column))
Expand All @@ -42,7 +42,7 @@ protected function buildColumnsDrop(array $columns):void
$this->migration->addDownCode($this->recordBuilder->addDbColumn($tableName, $column))
->addUpCode($this->recordBuilder->dropColumn($tableName, $column->name));
if (static::isEnum($column)) {
$this->migration->addDownCode($this->recordBuilder->createEnum($tableName, $column->name, $column->enumValues))
$this->migration->addDownCode($this->recordBuilder->createEnum($tableName, $column->name, $column->enumValues, $column->xEnumType))
->addUpCode($this->recordBuilder->dropEnum($tableName, $column->name));
}
}
Expand Down Expand Up @@ -92,15 +92,15 @@ protected function buildColumnChanges(ColumnSchema $current, ColumnSchema $desir
}
}
if ($isChangeToEnum) {
$this->migration->addUpCode($this->recordBuilder->createEnum($tableName, $desired->name, $desired->enumValues), true);
$this->migration->addUpCode($this->recordBuilder->createEnum($tableName, $desired->name, $desired->enumValues, $desired->xEnumType), true);
}
if ($isChangeFromEnum) {
$this->migration->addUpCode($this->recordBuilder->dropEnum($tableName, $current->name));
}

if ($isChangeFromEnum) {
$this->migration
->addDownCode($this->recordBuilder->createEnum($tableName, $current->name, $current->enumValues));
->addDownCode($this->recordBuilder->createEnum($tableName, $current->name, $current->enumValues, $current->xEnumType));
}
if ($isChangeToEnum) {
$this->migration->addDownCode($this->recordBuilder->dropEnum($tableName, $current->name), true);
Expand All @@ -125,6 +125,7 @@ protected function compareColumns(ColumnSchema $current, ColumnSchema $desired):
$this->modifyDesiredInContextOfCurrent($current, $desiredFromDb);
$this->modifyDesiredFromDbInContextOfDesired($desired, $desiredFromDb);

// TODO SK
foreach (['type', 'size', 'allowNull', 'defaultValue', 'enumValues'
, 'dbType', 'phpType'
, 'precision', 'scale', 'unsigned'
Expand Down Expand Up @@ -152,7 +153,7 @@ protected function createEnumMigrations():void
continue;
}
$this->migration
->addUpCode($this->recordBuilder->createEnum($tableAlias, $attr->columnName, $attr->enumValues), true)
->addUpCode($this->recordBuilder->createEnum($tableAlias, $attr->columnName, $attr->enumValues, $attr->xEnumType), true)
->addDownCode($this->recordBuilder->dropEnum($tableAlias, $attr->columnName), true);
}
}
Expand Down Expand Up @@ -248,4 +249,36 @@ public function modifyDesiredInContextOfCurrent(ColumnSchema $current, ColumnSch
$desired->size = $current->size;
}
}


// /**
// * Get PostgreSQL enum type name for a specific column.
// *
// * @param string $schema e.g. 'public'
// * @param string $table table name without schema quotes, e.g. 'suggestions'
// * @param string $column column name, e.g. 'type'
// * @return string|null enum type name like 'enum_suggestions_united_type' or null if not enum/user-defined
// */
// function pgEnumTypeName(string $schema, string $table, string $column): ?string
// {
// // information_schema exposes enum/domains as USER-DEFINED with udt_name = type name
// $sql = <<<SQL
//SELECT udt_name
//FROM information_schema.columns
//WHERE table_schema = :schema
// AND table_name = :table
// AND column_name = :column
// AND data_type = 'USER-DEFINED'
//LIMIT 1
//SQL;
//
// $type = Yii::$app->db->createCommand($sql, [
// ':schema' => $schema,
// ':table' => $table,
// ':column' => $column,
// ])->queryScalar();
//
// return $type !== false ? $type : null;
// }

}
16 changes: 16 additions & 0 deletions src/lib/openapi/PropertySchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,22 @@ public function isReadonly():bool

public function guessPhpType():string
{
if (isset($this->property->{'x-enum-type'})) {
return 'string';
}
//
//
// // Keep original DB type string (do NOT lowercase yet)
// $rawDbType = isset($this->property->{CustomSpecAttr::DB_TYPE})
// ? (string)$this->property->{CustomSpecAttr::DB_TYPE}
// : null;
//
// // --- Special-case: x-db-type: enum(<existing_pg_enum_type>)
// if ($rawDbType && preg_match('/^enum\(\s*("?[^")]+"?)\s*\)$/i', trim($rawDbType))) {
// // Treat as string in PHP regardless of DB enum
// return 'string';
// }

$customDbType = isset($this->property->{CustomSpecAttr::DB_TYPE})
? strtolower($this->property->{CustomSpecAttr::DB_TYPE}) : null;
if ($customDbType !== null
Expand Down