diff --git a/docker-compose.yml b/docker-compose.yml index 08d61292..2eda366e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: MYSQL_DATABASE: testdb maria: - image: mariadb:10.8 + image: mariadb:10.8.2 ports: - '23306:3306' volumes: diff --git a/src/lib/migrations/BaseMigrationBuilder.php b/src/lib/migrations/BaseMigrationBuilder.php index 2cf1702a..416311d9 100644 --- a/src/lib/migrations/BaseMigrationBuilder.php +++ b/src/lib/migrations/BaseMigrationBuilder.php @@ -199,9 +199,10 @@ function (string $unknownColumn) { $columnsForChange = array_intersect($wantNames, $haveNames); + $fromColNameToColName = $this->handleColumnsRename($columnsForCreate, $columnsForDrop, $this->newColumns); + if ($this->model->drop) { $this->newColumns = []; - $wantNames = []; $columnsForCreate = []; $columnsForChange = []; $columnsForDrop = []; @@ -218,7 +219,9 @@ function (string $unknownColumn) { } if (!$relation) { - $this->buildIndexChanges(); + $this->buildIndexChanges($fromColNameToColName); + } else { + $this->migrationForRenameColumn($fromColNameToColName); } $this->buildColumnsDrop($columnsForDrop); @@ -303,7 +306,7 @@ abstract protected function findTableIndexes():array; abstract public function handleCommentsMigration(); - protected function buildIndexChanges():void + protected function buildIndexChanges(array $fromColNameToColName): void { $haveIndexes = $this->findTableIndexes(); $wantIndexes = $this->model->indexes; @@ -338,6 +341,9 @@ function ($idx) use ($wantIndexes) { $this->migration->addUpCode($this->recordBuilder->dropIndex($tableName, $index->name)) ->addDownCode($downCode); } + + $this->migrationForRenameColumn($fromColNameToColName); + foreach ($forCreate as $index) { $upCode = $index->isUnique ? $this->recordBuilder->addUniqueIndex($tableName, $index->name, $index->columns) @@ -614,4 +620,60 @@ protected function shouldCompareComment(ColumnSchema $desired): bool } return $comment; } + + /** + * @param array $columnsForCreate + * @param array $columnsForDrop + * @param $newColumns + * @return array key is previous/old column name and value is new column name + */ + public function handleColumnsRename(array &$columnsForCreate, array &$columnsForDrop, $newColumns): array + { + $keys = []; + $fromColNameToColName = []; + $existingColumns = $this->tableSchema->columns; + if (count($existingColumns) !== count($newColumns)) { + return $fromColNameToColName; + } + $existingColumnNames = array_keys($existingColumns); + $newColumnNames = array_flip(array_keys($newColumns)); + foreach ($columnsForCreate as $key => $column) { + $index = $newColumnNames[$column->name]; + $previousColumnName = $existingColumnNames[$index] ?? null; + if ($previousColumnName) { + $current = $existingColumns[$previousColumnName]; + $desired = $newColumns[$column->name]; + $changedAttributes = $this->compareColumns(clone $current, clone $desired); + if (empty($changedAttributes)) { + $keys[] = $key; + $dropKeyOut = null; + array_walk($columnsForDrop, function ($value, $dropKey) use ($previousColumnName, &$dropKeyOut) { + if ($value->name === $previousColumnName) { + $dropKeyOut = $dropKey; + } + }); + // existing column name should be removed from $columnsForDrop + unset($columnsForDrop[$dropKeyOut]); + + // Create ALTER COLUMN NAME query + // see `migrationForRenameColumn()` + $fromColNameToColName[$previousColumnName] = $column->name; + } + } + } + + // new column name should be removed from $columnsForCreate + foreach ($keys as $key) { + unset($columnsForCreate[$key]); + } + return $fromColNameToColName; + } + + public function migrationForRenameColumn(array $fromColNameToColName): void + { + foreach ($fromColNameToColName as $previousColumnName => $columnName) { + $this->migration->addUpCode($this->recordBuilder->renameColumn($this->model->tableAlias, $previousColumnName, $columnName)) + ->addDownCode($this->recordBuilder->renameColumn($this->model->tableAlias, $columnName, $previousColumnName)); + } + } } diff --git a/src/lib/migrations/MigrationRecordBuilder.php b/src/lib/migrations/MigrationRecordBuilder.php index 700cbe4e..14df1f6c 100644 --- a/src/lib/migrations/MigrationRecordBuilder.php +++ b/src/lib/migrations/MigrationRecordBuilder.php @@ -49,6 +49,7 @@ final class MigrationRecordBuilder public const ADD_COMMENT_ON_COLUMN = MigrationRecordBuilder::INDENT . "\$this->addCommentOnColumn('%s', '%s', %s);"; public const DROP_COMMENT_FROM_COLUMN = MigrationRecordBuilder::INDENT . "\$this->dropCommentFromColumn('%s', '%s');"; + public const RENAME_COLUMN = MigrationRecordBuilder::INDENT . "\$this->renameColumn('%s', '%s', '%s');"; /** * @var \yii\db\Schema @@ -387,4 +388,9 @@ public function dropCommentFromColumn($table, string $column): string { return sprintf(self::DROP_COMMENT_FROM_COLUMN, $table, $column); } + + public function renameColumn(string $table, string $fromColumn, string $toColumn): string + { + return sprintf(self::RENAME_COLUMN, $table, $fromColumn, $toColumn); + } } diff --git a/tests/specs/change_column_name/maria/app/migrations_maria_db/m200000_000000_change_table_column_name_changes.php b/tests/specs/change_column_name/maria/app/migrations_maria_db/m200000_000000_change_table_column_name_changes.php index 1a75dbd3..06662558 100644 --- a/tests/specs/change_column_name/maria/app/migrations_maria_db/m200000_000000_change_table_column_name_changes.php +++ b/tests/specs/change_column_name/maria/app/migrations_maria_db/m200000_000000_change_table_column_name_changes.php @@ -7,13 +7,11 @@ class m200000_000000_change_table_column_name_changes extends \yii\db\Migration { public function up() { - $this->db->createCommand('ALTER TABLE {{%column_name_changes}} ADD COLUMN updated_at_2 datetime NOT NULL')->execute(); - $this->dropColumn('{{%column_name_changes}}', 'updated_at'); + $this->renameColumn('{{%column_name_changes}}', 'updated_at', 'updated_at_2'); } public function down() { - $this->addColumn('{{%column_name_changes}}', 'updated_at', $this->datetime()->notNull()); - $this->dropColumn('{{%column_name_changes}}', 'updated_at_2'); + $this->renameColumn('{{%column_name_changes}}', 'updated_at_2', 'updated_at'); } } diff --git a/tests/specs/change_column_name/mysql/app/migrations_mysql_db/m200000_000000_change_table_column_name_changes.php b/tests/specs/change_column_name/mysql/app/migrations_mysql_db/m200000_000000_change_table_column_name_changes.php index 1a75dbd3..06662558 100644 --- a/tests/specs/change_column_name/mysql/app/migrations_mysql_db/m200000_000000_change_table_column_name_changes.php +++ b/tests/specs/change_column_name/mysql/app/migrations_mysql_db/m200000_000000_change_table_column_name_changes.php @@ -7,13 +7,11 @@ class m200000_000000_change_table_column_name_changes extends \yii\db\Migration { public function up() { - $this->db->createCommand('ALTER TABLE {{%column_name_changes}} ADD COLUMN updated_at_2 datetime NOT NULL')->execute(); - $this->dropColumn('{{%column_name_changes}}', 'updated_at'); + $this->renameColumn('{{%column_name_changes}}', 'updated_at', 'updated_at_2'); } public function down() { - $this->addColumn('{{%column_name_changes}}', 'updated_at', $this->datetime()->notNull()); - $this->dropColumn('{{%column_name_changes}}', 'updated_at_2'); + $this->renameColumn('{{%column_name_changes}}', 'updated_at_2', 'updated_at'); } } diff --git a/tests/specs/change_column_name/pgsql/app/migrations_pgsql_db/m200000_000000_change_table_column_name_changes.php b/tests/specs/change_column_name/pgsql/app/migrations_pgsql_db/m200000_000000_change_table_column_name_changes.php index 803cb366..c7ef8866 100644 --- a/tests/specs/change_column_name/pgsql/app/migrations_pgsql_db/m200000_000000_change_table_column_name_changes.php +++ b/tests/specs/change_column_name/pgsql/app/migrations_pgsql_db/m200000_000000_change_table_column_name_changes.php @@ -7,13 +7,11 @@ class m200000_000000_change_table_column_name_changes extends \yii\db\Migration { public function safeUp() { - $this->db->createCommand('ALTER TABLE {{%column_name_changes}} ADD COLUMN "updated_at_2" timestamp NOT NULL')->execute(); - $this->dropColumn('{{%column_name_changes}}', 'updated_at'); + $this->renameColumn('{{%column_name_changes}}', 'updated_at', 'updated_at_2'); } public function safeDown() { - $this->addColumn('{{%column_name_changes}}', 'updated_at', $this->timestamp()->notNull()); - $this->dropColumn('{{%column_name_changes}}', 'updated_at_2'); + $this->renameColumn('{{%column_name_changes}}', 'updated_at_2', 'updated_at'); } } diff --git a/tests/specs/issue_fix/3_bug_add_remove_property_and_at_the_same_time_change_it_at_x_indexes/mysql/migrations_mysql_db/m200000_000000_change_table_addresses.php b/tests/specs/issue_fix/3_bug_add_remove_property_and_at_the_same_time_change_it_at_x_indexes/mysql/migrations_mysql_db/m200000_000000_change_table_addresses.php index d86cdcc9..2e135d86 100644 --- a/tests/specs/issue_fix/3_bug_add_remove_property_and_at_the_same_time_change_it_at_x_indexes/mysql/migrations_mysql_db/m200000_000000_change_table_addresses.php +++ b/tests/specs/issue_fix/3_bug_add_remove_property_and_at_the_same_time_change_it_at_x_indexes/mysql/migrations_mysql_db/m200000_000000_change_table_addresses.php @@ -7,17 +7,15 @@ class m200000_000000_change_table_addresses extends \yii\db\Migration { public function up() { - $this->addColumn('{{%addresses}}', 'postCode', $this->string(64)->null()->defaultValue(null)); $this->dropIndex('addresses_shortName_postalCode_key', '{{%addresses}}'); + $this->renameColumn('{{%addresses}}', 'postalCode', 'postCode'); $this->createIndex('addresses_shortName_postCode_key', '{{%addresses}}', ["shortName", "postCode"], true); - $this->dropColumn('{{%addresses}}', 'postalCode'); } public function down() { - $this->addColumn('{{%addresses}}', 'postalCode', $this->string(64)->null()->defaultValue(null)); $this->dropIndex('addresses_shortName_postCode_key', '{{%addresses}}'); + $this->renameColumn('{{%addresses}}', 'postCode', 'postalCode'); $this->createIndex('addresses_shortName_postalCode_key', '{{%addresses}}', ["shortName", "postalCode"], true); - $this->dropColumn('{{%addresses}}', 'postCode'); } } diff --git a/tests/specs/issue_fix/63_just_column_name_rename/index.php b/tests/specs/issue_fix/63_just_column_name_rename/index.php new file mode 100644 index 00000000..4b56a405 --- /dev/null +++ b/tests/specs/issue_fix/63_just_column_name_rename/index.php @@ -0,0 +1,13 @@ + '@specs/issue_fix/63_just_column_name_rename/index.yml', + 'generateUrls' => false, + 'generateModels' => false, + 'excludeModels' => [ + 'Error', + ], + 'generateControllers' => false, + 'generateMigrations' => true, + 'generateModelFaker' => false, // `generateModels` must be `true` in order to use `generateModelFaker` as `true` +]; diff --git a/tests/specs/issue_fix/63_just_column_name_rename/index.yml b/tests/specs/issue_fix/63_just_column_name_rename/index.yml new file mode 100644 index 00000000..64888952 --- /dev/null +++ b/tests/specs/issue_fix/63_just_column_name_rename/index.yml @@ -0,0 +1,25 @@ +openapi: 3.0.3 +info: + title: '63_just_column_name_rename' + version: 1.0.0 + +components: + schemas: + Fruit: + type: object + properties: + id: + type: integer + name_2: + type: string + description_2: + type: string + colour: + type: string + +paths: + '/': + get: + responses: + '200': + description: OK diff --git a/tests/specs/issue_fix/63_just_column_name_rename/mysql/migrations_mysql_db/m200000_000000_change_table_fruits.php b/tests/specs/issue_fix/63_just_column_name_rename/mysql/migrations_mysql_db/m200000_000000_change_table_fruits.php new file mode 100644 index 00000000..0db16b09 --- /dev/null +++ b/tests/specs/issue_fix/63_just_column_name_rename/mysql/migrations_mysql_db/m200000_000000_change_table_fruits.php @@ -0,0 +1,19 @@ +renameColumn('{{%fruits}}', 'name', 'name_2'); + $this->renameColumn('{{%fruits}}', 'description', 'description_2'); + } + + public function down() + { + $this->renameColumn('{{%fruits}}', 'description_2', 'description'); + $this->renameColumn('{{%fruits}}', 'name_2', 'name'); + } +} diff --git a/tests/specs/issue_fix/63_just_column_name_rename/pgsql/migrations_pgsql_db/m200000_000000_change_table_fruits.php b/tests/specs/issue_fix/63_just_column_name_rename/pgsql/migrations_pgsql_db/m200000_000000_change_table_fruits.php new file mode 100644 index 00000000..d1d26c5f --- /dev/null +++ b/tests/specs/issue_fix/63_just_column_name_rename/pgsql/migrations_pgsql_db/m200000_000000_change_table_fruits.php @@ -0,0 +1,19 @@ +renameColumn('{{%fruits}}', 'name', 'name_2'); + $this->renameColumn('{{%fruits}}', 'description', 'description_2'); + } + + public function safeDown() + { + $this->renameColumn('{{%fruits}}', 'description_2', 'description'); + $this->renameColumn('{{%fruits}}', 'name_2', 'name'); + } +} diff --git a/tests/unit/Issue58FixTest.php b/tests/unit/Issue58FixTest.php index b88f0921..d4613e67 100644 --- a/tests/unit/Issue58FixTest.php +++ b/tests/unit/Issue58FixTest.php @@ -4,7 +4,6 @@ use tests\DbTestCase; use Yii; -use yii\base\InvalidArgumentException; use yii\helpers\FileHelper; // This class contains tests for various issues present at GitHub @@ -900,7 +899,7 @@ public function test58Move1Add1Del1Col() description: type: boolean col_6: - type: boolean + type: integer paths: '/': get: @@ -919,7 +918,7 @@ class m200000_000000_change_table_fruits extends \yii\db\Migration { public function up() { - $this->addColumn('{{%fruits}}', 'col_6', $this->boolean()->null()->defaultValue(null)); + $this->addColumn('{{%fruits}}', 'col_6', $this->integer()->null()->defaultValue(null)); $this->dropColumn('{{%fruits}}', 'size'); $this->alterColumn('{{%fruits}}', 'colour', $this->tinyInteger(1)->null()->defaultValue(null)->after('id')); } @@ -962,7 +961,8 @@ public function test58Add1Del1ColAtSamePosition() name: type: boolean description_new: - type: boolean + type: integer + default: 7 colour: type: boolean size: @@ -985,7 +985,7 @@ class m200000_000000_change_table_fruits extends \yii\db\Migration { public function up() { - $this->addColumn('{{%fruits}}', 'description_new', $this->boolean()->null()->defaultValue(null)->after('name')); + $this->addColumn('{{%fruits}}', 'description_new', $this->integer()->null()->defaultValue(7)->after('name')); $this->dropColumn('{{%fruits}}', 'description'); } diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index b5d5868b..f1b2bfe8 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -742,7 +742,6 @@ public function test3BugAddRemovePropertyAndAtTheSameTimeChangeItAtXIndexes() $this->createTestTableFor3BugAddRemovePropertyAndAtTheSameTimeChangeItAtXIndexes(); $testFile = Yii::getAlias("@specs/issue_fix/3_bug_add_remove_property_and_at_the_same_time_change_it_at_x_indexes/index.php"); $this->runGenerator($testFile); - $this->runActualMigrations('mysql', 1); $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ 'recursive' => true, ]); @@ -750,6 +749,7 @@ public function test3BugAddRemovePropertyAndAtTheSameTimeChangeItAtXIndexes() 'recursive' => true, ]); $this->checkFiles($actualFiles, $expectedFiles); + $this->runActualMigrations('mysql', 1); $this->dropTestTableFor3BugAddRemovePropertyAndAtTheSameTimeChangeItAtXIndexes(); } @@ -916,4 +916,50 @@ public function test35ResolveTodoReCheckOptionsRouteInRestAction() $this->checkFiles($actualFiles, $expectedFiles); } + // https://github.com/php-openapi/yii2-openapi/issues/63 + public function test63JustColumnNameRename() + { + $testFile = Yii::getAlias("@specs/issue_fix/63_just_column_name_rename/index.php"); + + // MySQL + Yii::$app->db->createCommand('DROP TABLE IF EXISTS {{%fruits}}')->execute(); + Yii::$app->db->createCommand()->createTable('{{%fruits}}', [ + 'id' => 'pk', + 'name' => 'text', + 'description' => 'text', + 'colour' => 'text', + ])->execute(); + + $this->runGenerator($testFile); + $this->runActualMigrations('mysql', 1); + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/63_just_column_name_rename/mysql"), [ + 'recursive' => true, + ]); + $this->checkFiles($actualFiles, $expectedFiles); + Yii::$app->db->createCommand('DROP TABLE IF EXISTS {{%fruits}}')->execute(); + + // PgSQL + $this->changeDbToPgsql(); + Yii::$app->db->createCommand('DROP TABLE IF EXISTS {{%fruits}}')->execute(); + Yii::$app->db->createCommand()->createTable('{{%fruits}}', [ + 'id' => 'pk', + 'name' => 'text', + 'description' => 'text', + 'colour' => 'text', + ])->execute(); + $this->runGenerator($testFile, 'pgsql'); + $this->runActualMigrations('pgsql', 1); + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + 'except' => ['migrations_mysql_db'] + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/63_just_column_name_rename/pgsql"), [ + 'recursive' => true, + ]); + $this->checkFiles($actualFiles, $expectedFiles); + Yii::$app->db->createCommand('DROP TABLE IF EXISTS {{%fruits}}')->execute(); + } }