Skip to content

Commit a9e0312

Browse files
authored
Merge pull request #2 from SOHELAHMED7/timestamp-enum-x-db-type-bug-fix
Timestamp enum x db type bug fix
2 parents 7dee6b9 + 72c2a79 commit a9e0312

File tree

81 files changed

+1220
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1220
-121
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ up:
3333
docker-compose up -d
3434
echo "Waiting for mariadb to start up..."
3535
docker-compose exec -T mysql timeout 60s sh -c "while ! (mysql -udbuser -pdbpass -h maria --execute 'SELECT 1;' > /dev/null 2>&1); do echo -n '.'; sleep 0.1 ; done; echo 'ok'" || (docker-compose ps; docker-compose logs; exit 1)
36+
echo "Create another test DB for PgSQL ..." # created because of enum in PgSQL are different than MySQL
37+
docker-compose exec -T postgres bash -c "psql --username dbuser --dbname testdb -c 'create database pg_test_db_2;'; psql --username dbuser --dbname testdb -c 'grant all privileges on database pg_test_db_2 to dbuser;'" || (docker-compose ps; docker-compose logs; exit 1)
38+
3639

3740
cli:
3841
docker-compose exec php bash

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,8 @@ e.g. attribute = 'my_property'.
316316
nullable: false
317317
```
318318

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

322322
```yaml
323323
test_table:
@@ -329,6 +329,8 @@ It work on MariaDb.
329329
- three
330330
```
331331

332+
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.
333+
332334
### Handling of `numeric` (#numeric, #MariaDb)
333335
precision-default = 10
334336
scale-default = 2
@@ -372,9 +374,10 @@ Generated files:
372374

373375
# Development
374376

375-
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` .
377+
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` .
376378

377379
```bash
380+
cd tests
378381
./yii migrate-mysql/up
379382
./yii migrate-mysql/down 4
380383

src/lib/ColumnToCode.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ public function getCode(bool $quoted = false):string
148148
}
149149

150150
$code = $this->rawParts['type'] . ' ' . $this->rawParts['nullable'] . $default;
151-
if (ApiGenerator::isMysql() && $this->isEnum()) {
152-
return $quoted ? '"' . str_replace("\'", "'", $code) . '"' : $code;
151+
if ((ApiGenerator::isMysql() || ApiGenerator::isMariaDb()) && $this->isEnum()) {
152+
return $quoted ? "'" . $code . "'" : $code;
153153
}
154154
if (ApiGenerator::isPostgres() && $this->alterByXDbType) {
155155
return $quoted ? "'" . $this->rawParts['type'] . "'" : $this->rawParts['type'];
@@ -160,7 +160,7 @@ public function getCode(bool $quoted = false):string
160160
public function getAlterExpression(bool $addUsingExpression = false):string
161161
{
162162
if ($this->isEnum() && ApiGenerator::isPostgres()) {
163-
return "'" . sprintf('enum_%1$s USING %1$s::enum_%1$s', $this->column->name) . "'";
163+
return "'" . sprintf('enum_%1$s USING "%1$s"::enum_%1$s', $this->column->name) . "'";
164164
}
165165
if ($this->column->dbType === 'tsvector') {
166166
return "'" . $this->rawParts['type'] . "'";
@@ -270,7 +270,9 @@ public static function enumToString(array $enum):string
270270

271271
public static function mysqlEnumToString(array $enum):string
272272
{
273-
return implode(', ', array_map('self::wrapQuotes', $enum));
273+
return implode(', ', array_map(function ($aEnumValue) {
274+
return self::wrapQuotes($aEnumValue, '"');
275+
}, $enum));
274276
}
275277

276278
private function defaultValueJson(array $value):string
@@ -329,7 +331,7 @@ private function resolve():void
329331
$this->rawParts['type'] =
330332
$this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize);
331333
}
332-
334+
333335
$this->isBuiltinType = $this->raw ? false : $this->getIsBuiltinType($type, $dbType);
334336

335337
$this->resolveDefaultValue();
@@ -346,7 +348,7 @@ private function getIsBuiltinType($type, $dbType)
346348
return false;
347349
}
348350

349-
if ($this->isEnum() && ApiGenerator::isMariaDb()) {
351+
if ($this->isEnum()) {
350352
return false;
351353
}
352354
if ($this->fromDb === true) {
@@ -421,16 +423,18 @@ private function resolveDefaultValue():void
421423
break;
422424
default:
423425
$isExpression = StringHelper::startsWith($value, 'CURRENT')
426+
|| StringHelper::startsWith($value, 'current')
424427
|| StringHelper::startsWith($value, 'LOCAL')
425428
|| substr($value, -1, 1) === ')';
426429
if ($isExpression) {
427430
$this->fluentParts['default'] = 'defaultExpression("' . self::escapeQuotes((string)$value) . '")';
431+
$this->rawParts['default'] = $value;
428432
} else {
429433
$this->fluentParts['default'] = $expectInteger
430434
? 'defaultValue(' . $value . ')' : 'defaultValue("' . self::escapeQuotes((string)$value) . '")';
435+
$this->rawParts['default'] = $expectInteger ? $value : self::wrapQuotes($value);
431436
}
432-
$this->rawParts['default'] = $expectInteger ? $value : self::wrapQuotes($value);
433-
if (ApiGenerator::isMysql() && $this->isEnum()) {
437+
if ((ApiGenerator::isMysql() || ApiGenerator::isMariaDb()) && $this->isEnum()) {
434438
$this->rawParts['default'] = self::escapeQuotes($this->rawParts['default']);
435439
}
436440
}

src/lib/migrations/BaseMigrationBuilder.php

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace cebe\yii2openapi\lib\migrations;
99

10+
use cebe\yii2openapi\generator\ApiGenerator;
1011
use cebe\yii2openapi\lib\ColumnToCode;
1112
use cebe\yii2openapi\lib\items\DbModel;
1213
use cebe\yii2openapi\lib\items\ManyToManyRelation;
@@ -203,14 +204,6 @@ function (string $unknownColumn) {
203204
// do not adjust existing primary keys
204205
continue;
205206
}
206-
if (!empty($current->enumValues)) {
207-
$current->type = 'enum';
208-
$current->dbType = 'enum';
209-
}
210-
if (!empty($desired->enumValues)) {
211-
$desired->type = 'enum';
212-
$desired->dbType = 'enum';
213-
}
214207
$changedAttributes = $this->compareColumns($current, $desired);
215208
if (empty($changedAttributes)) {
216209
continue;
@@ -423,20 +416,39 @@ protected function isNeedUsingExpression(string $fromType, string $toType):bool
423416
public function tmpSaveNewCol(\cebe\yii2openapi\db\ColumnSchema $columnSchema): \yii\db\ColumnSchema
424417
{
425418
$tableName = 'tmp_table_';
419+
$db = 'db';
420+
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
421+
$db = 'pg_test_db_2';
422+
}
426423

427-
Yii::$app->db->createCommand('DROP TABLE IF EXISTS '.$tableName)->execute();
424+
Yii::$app->{$db}->createCommand('DROP TABLE IF EXISTS '.$tableName)->execute();
428425

429426
if (is_string($columnSchema->xDbType) && !empty($columnSchema->xDbType)) {
430427
$column = [$columnSchema->name.' '.$this->newColStr($columnSchema)];
431428
} else {
432429
$column = [$columnSchema->name => $this->newColStr($columnSchema)];
433430
}
434431

435-
Yii::$app->db->createCommand()->createTable($tableName, $column)->execute();
432+
// create enum if relevant
433+
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {
434+
$allEnumValues = $columnSchema->enumValues;
435+
$allEnumValues = array_map(function ($aValue) {
436+
return "'$aValue'";
437+
}, $allEnumValues);
438+
Yii::$app->{$db}->createCommand(
439+
'CREATE TYPE enum_'.$columnSchema->name.' AS ENUM('.implode(', ', $allEnumValues).')'
440+
)->execute();
441+
}
436442

437-
$table = Yii::$app->db->getTableSchema($tableName);
443+
Yii::$app->{$db}->createCommand()->createTable($tableName, $column)->execute();
438444

439-
Yii::$app->db->createCommand()->dropTable($tableName)->execute();
445+
$table = Yii::$app->{$db}->getTableSchema($tableName);
446+
447+
Yii::$app->{$db}->createCommand()->dropTable($tableName)->execute();
448+
449+
if (ApiGenerator::isPostgres() && static::isEnum($columnSchema)) {// drop enum
450+
Yii::$app->{$db}->createCommand('DROP TYPE enum_'.$columnSchema->name)->execute();
451+
}
440452

441453
return $table->columns[$columnSchema->name];
442454
}
@@ -446,4 +458,23 @@ public function newColStr(\cebe\yii2openapi\db\ColumnSchema $columnSchema): stri
446458
$ctc = new ColumnToCode(\Yii::$app->db->schema, $columnSchema, false, false, true);
447459
return ColumnToCode::undoEscapeQuotes($ctc->getCode());
448460
}
461+
462+
public static function isEnum(\yii\db\ColumnSchema $columnSchema): bool
463+
{
464+
if (!empty($columnSchema->enumValues) && is_array($columnSchema->enumValues)) {
465+
return true;
466+
}
467+
return false;
468+
}
469+
470+
public static function isEnumValuesChanged(
471+
\yii\db\ColumnSchema $current,
472+
\yii\db\ColumnSchema $desired
473+
): bool {
474+
if (static::isEnum($current) && static::isEnum($desired) &&
475+
$current->enumValues !== $desired->enumValues) {
476+
return true;
477+
}
478+
return false;
479+
}
449480
}

src/lib/migrations/MysqlMigrationBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ protected function buildColumnChanges(ColumnSchema $current, ColumnSchema $desir
2929
foreach ($changed as $attr) {
3030
$newColumn->$attr = $desired->$attr;
3131
}
32-
if (!empty($newColumn->enumValues)) {
33-
$newColumn->dbType = 'enum';
32+
if (static::isEnum($newColumn)) {
33+
$newColumn->dbType = 'enum'; // TODO this is concretely not correct
3434
}
3535
$this->migration->addUpCode($this->recordBuilder->alterColumn($this->model->getTableAlias(), $newColumn))
3636
->addDownCode($this->recordBuilder->alterColumn($this->model->getTableAlias(), $current));

src/lib/migrations/PostgresMigrationBuilder.php

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ protected function buildColumnsCreation(array $columns):void
2222
{
2323
foreach ($columns as $column) {
2424
$tableName = $this->model->getTableAlias();
25-
if ($column->dbType === 'enum') {
25+
if (static::isEnum($column)) {
2626
$this->migration->addUpCode($this->recordBuilder->createEnum($column->name, $column->enumValues))
27-
->addDownCode($this->recordBuilder->dropEnum($column->name));
27+
->addDownCode($this->recordBuilder->dropEnum($column->name), true);
2828
}
2929
$this->migration->addUpCode($this->recordBuilder->addColumn($tableName, $column))
3030
->addDownCode($this->recordBuilder->dropColumn($tableName, $column->name));
@@ -41,9 +41,9 @@ protected function buildColumnsDrop(array $columns):void
4141
$tableName = $this->model->getTableAlias();
4242
$this->migration->addDownCode($this->recordBuilder->addDbColumn($tableName, $column))
4343
->addUpCode($this->recordBuilder->dropColumn($tableName, $column->name));
44-
if ($column->dbType === 'enum') {
44+
if (static::isEnum($column)) {
4545
$this->migration->addDownCode($this->recordBuilder->createEnum($column->name, $column->enumValues))
46-
->addUpCode($this->recordBuilder->dropEnum($column->name), true);
46+
->addUpCode($this->recordBuilder->dropEnum($column->name));
4747
}
4848
}
4949
}
@@ -54,22 +54,20 @@ protected function buildColumnsDrop(array $columns):void
5454
protected function buildColumnChanges(ColumnSchema $current, ColumnSchema $desired, array $changed):void
5555
{
5656
$tableName = $this->model->getTableAlias();
57-
$isChangeToEnum = $current->type !== $desired->type && !empty($desired->enumValues);
58-
$isChangeFromEnum = $current->type !== $desired->type && !empty($current->enumValues);
59-
$isChangedEnum = $current->type === $desired->type && !empty($current->enumValues);
57+
$isChangeToEnum = !static::isEnum($current) && static::isEnum($desired);
58+
$isChangeFromEnum = static::isEnum($current) && !static::isEnum($desired);
59+
$isChangedEnum = static::isEnumValuesChanged($current, $desired);
6060
if ($isChangedEnum) {
6161
// Generation for change enum values not supported. Do it manually
6262
// This action require several steps and can't be applied during single transaction
6363
return;
6464
}
65-
if ($isChangeToEnum) {
66-
$this->migration->addUpCode($this->recordBuilder->createEnum($desired->name, $desired->enumValues), true);
67-
}
68-
if ($isChangeFromEnum) {
69-
$this->migration->addUpCode($this->recordBuilder->dropEnum($current->name));
70-
}
71-
if (!empty(array_intersect(['type', 'size'], $changed))) {
72-
$addUsing = $this->isNeedUsingExpression($desired->type, $current->type);
65+
66+
if (!empty(array_intersect(['type', 'size'
67+
, 'dbType', 'phpType'
68+
, 'precision', 'scale', 'unsigned'
69+
], $changed))) {
70+
$addUsing = $this->isNeedUsingExpression($current->type, $desired->type);
7371
$this->migration->addUpCode($this->recordBuilder->alterColumnType($tableName, $desired));
7472
$this->migration->addDownCode($this->recordBuilder->alterColumnTypeFromDb($tableName, $current, $addUsing));
7573
}
@@ -93,9 +91,16 @@ protected function buildColumnChanges(ColumnSchema $current, ColumnSchema $desir
9391
$this->migration->addUpCode($upCode)->addDownCode($downCode, true);
9492
}
9593
}
94+
if ($isChangeToEnum) {
95+
$this->migration->addUpCode($this->recordBuilder->createEnum($desired->name, $desired->enumValues), true);
96+
}
97+
if ($isChangeFromEnum) {
98+
$this->migration->addUpCode($this->recordBuilder->dropEnum($current->name));
99+
}
100+
96101
if ($isChangeFromEnum) {
97102
$this->migration
98-
->addDownCode($this->recordBuilder->createEnum($current->name, $current->enumValues), true);
103+
->addDownCode($this->recordBuilder->createEnum($current->name, $current->enumValues));
99104
}
100105
if ($isChangeToEnum) {
101106
$this->migration->addDownCode($this->recordBuilder->dropEnum($current->name), true);
@@ -132,7 +137,7 @@ protected function createEnumMigrations():void
132137
foreach ($enums as $attr) {
133138
$this->migration
134139
->addUpCode($this->recordBuilder->createEnum($attr->columnName, $attr->enumValues), true)
135-
->addDownCode($this->recordBuilder->dropEnum($attr->columnName));
140+
->addDownCode($this->recordBuilder->dropEnum($attr->columnName), true);
136141
}
137142
}
138143

src/lib/openapi/PropertySchema.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ public static function findMoreDetailOf(string $xDbType): array
530530
}
531531

532532
/**
533-
* This method is copied from protected method `getColumnPhpType()` of \yii\db\Schema class
533+
* This method is copied + enhanced from protected method `getColumnPhpType()` of \yii\db\Schema class
534534
* Extracts the PHP type from abstract DB type.
535535
* @param \yii\db\ColumnSchema $column the column schema information
536536
* @return string PHP type name
@@ -546,6 +546,7 @@ public static function getColumnPhpType(ColumnSchema $column): string
546546
YiiDbSchema::TYPE_BOOLEAN => 'boolean',
547547
YiiDbSchema::TYPE_FLOAT => 'double',
548548
YiiDbSchema::TYPE_DOUBLE => 'double',
549+
YiiDbSchema::TYPE_DECIMAL => 'double', # (enhanced)
549550
YiiDbSchema::TYPE_BINARY => 'resource',
550551
YiiDbSchema::TYPE_JSON => 'array',
551552
];

tests/DbTestCase.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,19 @@ protected function changeDbToPgsql()
8989
self::assertNotInstanceOf(MySqlSchema::class, Yii::$app->db->schema);
9090
self::assertInstanceOf(PgSqlSchema::class, Yii::$app->db->schema);
9191
}
92+
93+
protected function checkFiles(array $actual, array $expected)
94+
{
95+
self::assertEquals(
96+
count($actual),
97+
count($expected)
98+
);
99+
foreach ($actual as $index => $file) {
100+
$expectedFilePath = $expected[$index];
101+
self::assertFileExists($file);
102+
self::assertFileExists($expectedFilePath);
103+
104+
$this->assertFileEquals($expectedFilePath, $file, "Failed asserting that file contents of\n$file\nare equal to file contents of\n$expectedFilePath");
105+
}
106+
}
92107
}

tests/config/console.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@
5353
'charset' => 'utf8',
5454
'tablePrefix'=>'itt_',
5555
],
56+
'pg_test_db_2' => [
57+
'class' => \yii\db\Connection::class,
58+
'dsn' => 'pgsql:host=postgres;dbname=pg_test_db_2',
59+
'username' => 'dbuser',
60+
'password' => 'dbpass',
61+
'charset' => 'utf8',
62+
],
5663
'mysql' => [
5764
'class' => \yii\db\Connection::class,
5865
'dsn' => 'mysql:host=mysql;dbname=testdb',

tests/fixtures/blog.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
->setSize(200)->setRequired()->setFakerStub('substr($faker->safeEmail, 0, 200)'),
2020
'password' => (new Attribute('password', ['phpType' => 'string', 'dbType' => 'string']))
2121
->setRequired()->setFakerStub('$faker->password'),
22+
'role' => (new Attribute('role', ['phpType' => 'string', 'dbType' => 'string']))
23+
->setSize(20)
24+
->setDefault('reader')
25+
->setFakerStub('$faker->randomElement([\'admin\', \'editor\', \'reader\'])'),
2226
'flags' => (new Attribute('flags', ['phpType'=>'int', 'dbType'=>'integer']))->setDefault(0)->setFakerStub
2327
('$faker->numberBetween(0, 1000000)'),
2428
'created_at' => (new Attribute('created_at', ['phpType' => 'string', 'dbType' => 'datetime']))
@@ -28,7 +32,7 @@
2832
'indexes' => [
2933
'users_email_key' => DbIndex::make('users', ['email'], null, true),
3034
'users_username_key' => DbIndex::make('users', ['username'], null, true),
31-
'users_flags_index' => DbIndex::make('users', ['flags'])
35+
'users_role_flags_index' => DbIndex::make('users', ['role', 'flags'])
3236
]
3337
]),
3438
'category' => new DbModel([

0 commit comments

Comments
 (0)