Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Merge PRs of SOHELAHMED7/yii2-openapi #124

Merged
merged 77 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
08e30ab
Test
SOHELAHMED7 Dec 21, 2022
e367a47
Merge branch '100-timestamp-migrations-do-not-work-in-mariadb' of git…
SOHELAHMED7 Dec 21, 2022
ec097d7
Test 2
SOHELAHMED7 Dec 21, 2022
03ca98b
test 3
SOHELAHMED7 Dec 21, 2022
7dee6b9
Merge pull request #1 from SOHELAHMED7/100-timestamp-migrations-do-no…
SOHELAHMED7 Dec 21, 2022
6c90780
Merge branch 'master' of https://github.com/SOHELAHMED7/yii2-openapi …
SOHELAHMED7 Dec 21, 2022
984c7b3
Test
SOHELAHMED7 Dec 21, 2022
96257f6
Cleanup
SOHELAHMED7 Dec 21, 2022
d3ecd74
test
SOHELAHMED7 Dec 21, 2022
c809394
Fix issue in setting default expression
SOHELAHMED7 Dec 21, 2022
3d4d71c
Fix issue: decimal considered as string instead of double/float
SOHELAHMED7 Dec 21, 2022
a9c6ec2
Fix enum issue for MySQL, MariaDB and partially for PgSQL #111
SOHELAHMED7 Dec 22, 2022
89f98c2
WIP
SOHELAHMED7 Dec 23, 2022
b047f7d
undo WIP
SOHELAHMED7 Dec 23, 2022
6cea7f2
Fix issue: CREATE/DROP enum in PGSQL up/down migration not in correct…
SOHELAHMED7 Dec 23, 2022
6a9a680
Fix style
SOHELAHMED7 Dec 23, 2022
c60403f
Fix quote issue in migration down code for MySQL and Maria + add more…
SOHELAHMED7 Dec 23, 2022
fcc53a2
Fix multiple issue + add more tests for eunm in Pgsql + other DB
SOHELAHMED7 Dec 23, 2022
130c69a
Fix bug in column change detection in Pgsql
SOHELAHMED7 Dec 24, 2022
1b0bc2e
Fix failing tests
SOHELAHMED7 Dec 24, 2022
a037827
Fix enum related issues + add more tests for it for all 3 DBs
SOHELAHMED7 Dec 24, 2022
dae8e4c
Implement Enum change value migration - WIP
SOHELAHMED7 Dec 24, 2022
e67e8e1
WIP
SOHELAHMED7 Dec 24, 2022
ded2d2b
Complete enum tests + add stub for enum values change test
SOHELAHMED7 Dec 27, 2022
af614eb
Remove unwanted files
SOHELAHMED7 Dec 27, 2022
39d2ff0
Add docs in README
SOHELAHMED7 Dec 27, 2022
5badea4
Restore tests that were deleted in https://github.com/cebe/yii2-opena…
SOHELAHMED7 Dec 27, 2022
72c2a79
Add enh.
SOHELAHMED7 Dec 27, 2022
a9e0312
Merge pull request #2 from SOHELAHMED7/timestamp-enum-x-db-type-bug-fix
SOHELAHMED7 Dec 28, 2022
c9322ad
test
SOHELAHMED7 Dec 28, 2022
a8d3b61
Undo test
SOHELAHMED7 Dec 28, 2022
c0563e7
Fix issue: defaultValue change detection for expression was not worki…
SOHELAHMED7 Dec 28, 2022
921f6ed
Merge pull request #3 from SOHELAHMED7/json-issue-fix
SOHELAHMED7 Dec 29, 2022
20e960a
test to trigger CI
SOHELAHMED7 Dec 29, 2022
4066e93
test to trigger CI 2
SOHELAHMED7 Dec 29, 2022
ca72322
test to trigger CI 3
SOHELAHMED7 Dec 29, 2022
672ae45
test to trigger CI 4
SOHELAHMED7 Dec 29, 2022
7da7317
Fix style
SOHELAHMED7 Dec 29, 2022
53d4de4
test to trigger CI 5
SOHELAHMED7 Dec 29, 2022
0041f7b
Fix style
SOHELAHMED7 Dec 29, 2022
a2d0c80
Fix https://github.com/cebe/yii2-openapi/issues/104
SOHELAHMED7 Dec 30, 2022
1f1e71f
remove unwanted files
SOHELAHMED7 Dec 30, 2022
86ae33d
Merge pull request #4 from SOHELAHMED7/104-remove-id-from-rules-metho…
SOHELAHMED7 Dec 30, 2022
8c074a9
Fix https://github.com/cebe/yii2-openapi/issues/115
SOHELAHMED7 Dec 30, 2022
512a11e
Merge pull request #5 from SOHELAHMED7/115-default-in-openapi-schema-…
SOHELAHMED7 Dec 30, 2022
91273e8
test
SOHELAHMED7 Dec 30, 2022
0ac648d
Add tests for this issue
SOHELAHMED7 Dec 30, 2022
d7e57a0
remove unwanted space from README
SOHELAHMED7 Dec 30, 2022
0eae0f4
Merge pull request #6 from SOHELAHMED7/114-errors-in-migrations-in-ca…
SOHELAHMED7 Dec 30, 2022
4aad200
Merge branch 'cebe:master' into master
SOHELAHMED7 Dec 30, 2022
220d1ee
Test
SOHELAHMED7 Dec 30, 2022
c73c16d
Fix https://github.com/cebe/yii2-openapi/issues/125
SOHELAHMED7 Dec 30, 2022
a6fc5b1
Run actual migrations
SOHELAHMED7 Dec 31, 2022
09a5fb8
Add a test for float issue in PgSQL
SOHELAHMED7 Dec 31, 2022
67ad00c
test
SOHELAHMED7 Dec 31, 2022
2e382e4
Undo test
SOHELAHMED7 Dec 31, 2022
a8d8788
WIP
SOHELAHMED7 Dec 31, 2022
433e7f0
Fix https://github.com/cebe/yii2-openapi/issues/126
SOHELAHMED7 Jan 2, 2023
ce56996
Merge pull request #8 from SOHELAHMED7/126-dont-use-second-db-pgtestd…
SOHELAHMED7 Jan 2, 2023
60dd357
Merge branches 'master' and '107-migrations-are-generated-with-syntax…
SOHELAHMED7 Jan 2, 2023
d0b83eb
Remove unwanted line
SOHELAHMED7 Jan 2, 2023
9e341e9
Initial commit of this branch
SOHELAHMED7 Jan 2, 2023
4da1db2
Fix https://github.com/cebe/yii2-openapi/issues/127
SOHELAHMED7 Jan 2, 2023
2f58a9e
Fix quote issue in enum in PgSQL
SOHELAHMED7 Jan 2, 2023
e8f1df1
Fix quote issue in enum in PgSQL 2
SOHELAHMED7 Jan 2, 2023
eaf2464
Merge pull request #9 from SOHELAHMED7/127-quote-issue-to-preserve-th…
SOHELAHMED7 Jan 3, 2023
a37e2ca
Merge branches 'master' and '107-migrations-are-generated-with-syntax…
SOHELAHMED7 Jan 3, 2023
a6c20ef
Fix failing tests
SOHELAHMED7 Jan 3, 2023
3fff89a
Fix https://github.com/cebe/yii2-openapi/issues/107
SOHELAHMED7 Jan 3, 2023
1cdb53b
Merge pull request #7 from SOHELAHMED7/107-migrations-are-generated-w…
SOHELAHMED7 Jan 3, 2023
8aa5697
Initial commit of this branch to create PR
SOHELAHMED7 Jan 3, 2023
dcbde27
Add tests
SOHELAHMED7 Jan 3, 2023
123e67a
Implement quote in enum type names
SOHELAHMED7 Jan 3, 2023
d4527f5
Implement enum with table name
SOHELAHMED7 Jan 3, 2023
d4f6f2d
Adjust tests for enum with table name
SOHELAHMED7 Jan 4, 2023
e2fc2e9
Merge pull request #10 from SOHELAHMED7/128-change-enum-name-generati…
SOHELAHMED7 Jan 4, 2023
8250c09
Fix errors in https://github.com/SOHELAHMED7/yii2-openapi/pull/10
SOHELAHMED7 Jan 4, 2023
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ e.g. attribute = 'my_property'.
nullable: false
```

### Handling of `enum` (#enum, #MariaDb)
It work on MariaDb.
### Handling of `enum` (#enum)
It works on all 3 DB: MySQL, MariaDb and PgSQL.

```yaml
test_table:
Expand All @@ -329,6 +329,8 @@ It work on MariaDb.
- three
```

Note: Change in enum values are not very simple. For Mysql and Mariadb, migrations will be generated but in many cases custom modification in it are required. For Pgsql migrations for change in enum values will not be generated. It should be handled manually.

### Handling of `numeric` (#numeric, #MariaDb)
precision-default = 10
scale-default = 2
Expand Down Expand Up @@ -372,9 +374,10 @@ Generated files:

# Development

There commands are available to develop and check the tests. It can be used inside the Docker container. To enter into bash of container run `make cli` .
There commands are available to develop and check the tests. It is available inside the Docker container. To enter into bash shell of container, run `make cli` .

```bash
cd tests
./yii migrate-mysql/up
./yii migrate-mysql/down 4

Expand Down Expand Up @@ -405,4 +408,3 @@ Professional support, consulting as well as software development services are av
https://www.cebe.cc/en/contact

Development of this library is sponsored by [cebe.:cloud: "Your Professional Deployment Platform"](https://cebe.cloud).

38 changes: 28 additions & 10 deletions src/lib/ColumnToCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ class ColumnToCode
*/
private $dbSchema;

/**
* @var string
* @example {{%table}}
*/
private $tableAlias;

/**
* @var bool
*/
Expand Down Expand Up @@ -107,13 +113,15 @@ class ColumnToCode
*/
public function __construct(
Schema $dbSchema,
string $tableAlias,
ColumnSchema $column,
bool $fromDb = false,
bool $alter = false,
bool $raw = false,
bool $alterByXDbType = false
) {
$this->dbSchema = $dbSchema;
$this->tableAlias = $tableAlias;
$this->column = $column;
$this->fromDb = $fromDb;
$this->alter = $alter;
Expand Down Expand Up @@ -148,8 +156,8 @@ public function getCode(bool $quoted = false):string
}

$code = $this->rawParts['type'] . ' ' . $this->rawParts['nullable'] . $default;
if (ApiGenerator::isMysql() && $this->isEnum()) {
return $quoted ? '"' . str_replace("\'", "'", $code) . '"' : $code;
if ((ApiGenerator::isMysql() || ApiGenerator::isMariaDb()) && $this->isEnum()) {
return $quoted ? "'" . $code . "'" : $code;
}
if (ApiGenerator::isPostgres() && $this->alterByXDbType) {
return $quoted ? "'" . $this->rawParts['type'] . "'" : $this->rawParts['type'];
Expand All @@ -160,13 +168,18 @@ public function getCode(bool $quoted = false):string
public function getAlterExpression(bool $addUsingExpression = false):string
{
if ($this->isEnum() && ApiGenerator::isPostgres()) {
return "'" . sprintf('enum_%1$s USING %1$s::enum_%1$s', $this->column->name) . "'";
$rawTableName = $this->dbSchema->getRawTableName($this->tableAlias);
$enumTypeName = 'enum_'.$rawTableName.'_'.$this->column->name;
return "'" . sprintf('"'.$enumTypeName.'" USING "%1$s"::"'.$enumTypeName.'"', $this->column->name) . "'";
}
if ($this->column->dbType === 'tsvector') {
return "'" . $this->rawParts['type'] . "'";
}
if ($addUsingExpression && ApiGenerator::isPostgres()) {
return "'" . $this->rawParts['type'] . " ".$this->rawParts['nullable']
return "'" . $this->rawParts['type'] .
($this->alterByXDbType ?
'' :
" ".$this->rawParts['nullable'])
.' USING "'.$this->column->name.'"::'.$this->typeWithoutSize($this->rawParts['type'])."'";
}

Expand Down Expand Up @@ -270,7 +283,9 @@ public static function enumToString(array $enum):string

public static function mysqlEnumToString(array $enum):string
{
return implode(', ', array_map('self::wrapQuotes', $enum));
return implode(', ', array_map(function ($aEnumValue) {
return self::wrapQuotes($aEnumValue, '"');
}, $enum));
}

private function defaultValueJson(array $value):string
Expand Down Expand Up @@ -329,7 +344,7 @@ private function resolve():void
$this->rawParts['type'] =
$this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize);
}

$this->isBuiltinType = $this->raw ? false : $this->getIsBuiltinType($type, $dbType);

$this->resolveDefaultValue();
Expand All @@ -346,7 +361,7 @@ private function getIsBuiltinType($type, $dbType)
return false;
}

if ($this->isEnum() && ApiGenerator::isMariaDb()) {
if ($this->isEnum()) {
return false;
}
if ($this->fromDb === true) {
Expand All @@ -363,7 +378,8 @@ private function getIsBuiltinType($type, $dbType)
private function resolveEnumType():void
{
if (ApiGenerator::isPostgres()) {
$this->rawParts['type'] = 'enum_' . $this->column->name;
$rawTableName = $this->dbSchema->getRawTableName($this->tableAlias);
$this->rawParts['type'] = '"enum_'.$rawTableName.'_' . $this->column->name.'"';
return;
}
$this->rawParts['type'] = 'enum(' . self::mysqlEnumToString($this->column->enumValues) . ')';
Expand Down Expand Up @@ -421,16 +437,18 @@ private function resolveDefaultValue():void
break;
default:
$isExpression = StringHelper::startsWith($value, 'CURRENT')
|| StringHelper::startsWith($value, 'current')
|| StringHelper::startsWith($value, 'LOCAL')
|| substr($value, -1, 1) === ')';
if ($isExpression) {
$this->fluentParts['default'] = 'defaultExpression("' . self::escapeQuotes((string)$value) . '")';
$this->rawParts['default'] = $value;
} else {
$this->fluentParts['default'] = $expectInteger
? 'defaultValue(' . $value . ')' : 'defaultValue("' . self::escapeQuotes((string)$value) . '")';
$this->rawParts['default'] = $expectInteger ? $value : self::wrapQuotes($value);
}
$this->rawParts['default'] = $expectInteger ? $value : self::wrapQuotes($value);
if (ApiGenerator::isMysql() && $this->isEnum()) {
if ((ApiGenerator::isMysql() || ApiGenerator::isMariaDb()) && $this->isEnum()) {
$this->rawParts['default'] = self::escapeQuotes($this->rawParts['default']);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/lib/ValidationRulesBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ public function build():array
}
}
foreach ($this->model->attributes as $attribute) {
// column/field/property with name `id` is considered as Primary Key by this library and it is automatically handled by DB/Yii; so remove it from validation `rules()`
if ($attribute->columnName === 'id' || $attribute->propertyName === 'id') {
continue;
}
$this->resolveAttributeRules($attribute);
}

Expand Down Expand Up @@ -181,6 +185,10 @@ private function prepareTypeScope():void
if ($attribute->isReadOnly()) {
continue;
}
// column/field/property with name `id` is considered as Primary Key by this library and it is automatically handled by DB/Yii; so remove it from validation `rules()`
if ($attribute->columnName === 'id' || $attribute->propertyName === 'id') {
continue;
}
if ($attribute->defaultValue === null && $attribute->isRequired()) {
$this->typeScope['required'][$attribute->columnName] = $attribute->columnName;
}
Expand Down
115 changes: 88 additions & 27 deletions src/lib/migrations/BaseMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace cebe\yii2openapi\lib\migrations;

use cebe\yii2openapi\generator\ApiGenerator;
use cebe\yii2openapi\lib\ColumnToCode;
use cebe\yii2openapi\lib\items\DbModel;
use cebe\yii2openapi\lib\items\ManyToManyRelation;
Expand All @@ -15,6 +16,7 @@
use yii\db\ColumnSchema;
use yii\helpers\VarDumper;
use yii\db\Connection;
use yii\db\Expression;

abstract class BaseMigrationBuilder
{
Expand Down Expand Up @@ -203,14 +205,6 @@ function (string $unknownColumn) {
// do not adjust existing primary keys
continue;
}
if (!empty($current->enumValues)) {
$current->type = 'enum';
$current->dbType = 'enum';
}
if (!empty($desired->enumValues)) {
$desired->type = 'enum';
$desired->dbType = 'enum';
}
$changedAttributes = $this->compareColumns($current, $desired);
if (empty($changedAttributes)) {
continue;
Expand Down Expand Up @@ -406,44 +400,111 @@ protected function unPrefixTableName(string $tableName):string
return str_replace($this->db->tablePrefix, '', $tableName);
}

protected function isNeedUsingExpression(string $fromType, string $toType):bool
protected function isNeedUsingExpression(string $fromDbType, string $toDbType):bool
{
$strings = ['string', 'text', 'char'];
if (in_array($fromType, $strings) && in_array($toType, $strings)) {
return false;
}
$ints = ['smallint', 'integer', 'bigint', 'float', 'decimal'];
if (in_array($fromType, $ints) && in_array($toType, $ints)) {
if ($fromDbType === $toDbType) {
return false;
}
$dates = ['date', 'timestamp'];
return !(in_array($fromType, $dates) && in_array($toType, $dates));
return true;
}

public function tmpSaveNewCol(\cebe\yii2openapi\db\ColumnSchema $columnSchema): \yii\db\ColumnSchema
// temporary save new/changed/desired column to temporary table. If saved we can fetch it from DB and then it can be used to compare with current column
public function tmpSaveNewCol(string $tableAlias, \cebe\yii2openapi\db\ColumnSchema $columnSchema): \yii\db\ColumnSchema
{
$tableName = 'tmp_table_';
$tmpTableName = 'tmp_table_';
$tmpEnumName = function (string $columnName): string {
return '"tmp_enum_'.$columnName.'_"';
};
$rawTableName = $this->db->schema->getRawTableName($tableAlias);
$innerEnumTypeName = "\"enum_{$tmpTableName}_{$columnSchema->name}\"";

Yii::$app->db->createCommand('DROP TABLE IF EXISTS '.$tableName)->execute();
Yii::$app->db->createCommand('DROP TABLE IF EXISTS '.$tmpTableName)->execute();

if (is_string($columnSchema->xDbType) && !empty($columnSchema->xDbType)) {
$column = [$columnSchema->name.' '.$this->newColStr($columnSchema)];
$name = MigrationRecordBuilder::quote($columnSchema->name);
$column = [$name.' '.$this->newColStr($tmpTableName, $columnSchema)];
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
$column = strtr($column, [$innerEnumTypeName => $tmpEnumName($columnSchema->name)]);
}
} else {
$column = [$columnSchema->name => $this->newColStr($columnSchema)];
$column = [$columnSchema->name => $this->newColStr($tmpTableName, $columnSchema)];
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
$column[$columnSchema->name] = strtr($column[$columnSchema->name], [$innerEnumTypeName => $tmpEnumName($columnSchema->name)]);
}
}

Yii::$app->db->createCommand()->createTable($tableName, $column)->execute();
// create enum if relevant
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
$allEnumValues = $columnSchema->enumValues;
$allEnumValues = array_map(function ($aValue) {
return "'$aValue'";
}, $allEnumValues);
Yii::$app->db->createCommand(
'CREATE TYPE '.$tmpEnumName($columnSchema->name).' AS ENUM('.implode(', ', $allEnumValues).')'
)->execute();
}

Yii::$app->db->createCommand()->createTable($tmpTableName, $column)->execute();

$table = Yii::$app->db->getTableSchema($tmpTableName);

$table = Yii::$app->db->getTableSchema($tableName);
Yii::$app->db->createCommand()->dropTable($tmpTableName)->execute();

Yii::$app->db->createCommand()->dropTable($tableName)->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);
}
// 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;
}

return $table->columns[$columnSchema->name];
}

public function newColStr(\cebe\yii2openapi\db\ColumnSchema $columnSchema): string
public function newColStr(string $tableAlias, \cebe\yii2openapi\db\ColumnSchema $columnSchema): string
{
$ctc = new ColumnToCode(\Yii::$app->db->schema, $columnSchema, false, false, true);
$ctc = new ColumnToCode(\Yii::$app->db->schema, $tableAlias, $columnSchema, false, false, true);
return ColumnToCode::undoEscapeQuotes($ctc->getCode());
}

public static function isEnum(\yii\db\ColumnSchema $columnSchema): bool
{
if (!empty($columnSchema->enumValues) && is_array($columnSchema->enumValues)) {
return true;
}
return false;
}

public static function isEnumValuesChanged(
\yii\db\ColumnSchema $current,
\yii\db\ColumnSchema $desired
): bool {
if (static::isEnum($current) && static::isEnum($desired) &&
$current->enumValues !== $desired->enumValues) {
return true;
}
return false;
}

public function isDefaultValueChanged(
ColumnSchema $current,
ColumnSchema $desired
): bool {
// if the default value is object of \yii\db\Expression then default value is expression instead of constant. See https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
// in such case instead of comparing two objects, we should compare expression

if ($current->defaultValue instanceof Expression &&
$desired->defaultValue instanceof Expression
&& $current->defaultValue->expression === $desired->defaultValue->expression
) {
return false;
}

if ($current->defaultValue !== $desired->defaultValue) {
return true;
}
return false;
}
}
Loading