[BUGFIX] InstallTool: handle column renames in database compare 85/51085/6
authorMorton Jonuschat <m.jonuschat@mojocode.de>
Sat, 31 Dec 2016 21:07:31 +0000 (13:07 -0800)
committerChristian Kuhn <lolli@schwarzbu.ch>
Tue, 10 Jan 2017 12:42:55 +0000 (13:42 +0100)
When the Doctrine SchemaMigrator detects an added column and a removed
column that only differ by name but not by configuration these separate
changes get optimized into a column rename operation.

Deoptimize these renames into distinct add/drop column operations to
keep handling of columns consistent when multiple columns change at the
same time.

Resolves: #78771
Releases: master
Change-Id: Ic6d06b99dfb3f33975969be39d904faac5b438a0
Reviewed-on: https://review.typo3.org/51085
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php

index 78e3897..46f63bc 100644 (file)
@@ -42,7 +42,7 @@ class ConnectionMigrator
     protected $deletedPrefix = 'zzz_deleted_';
 
     /**
-     * @var int
+     * @var array
      */
     protected $tableAndFieldMaxNameLengthsPerDbPlatform = [
         'default' => [
@@ -249,6 +249,7 @@ class ConnectionMigrator
         // Build SchemaDiff and handle renames of tables and colums
         $comparator = GeneralUtility::makeInstance(Comparator::class);
         $schemaDiff = $comparator->compare($fromSchema, $toSchema);
+        $schemaDiff = $this->migrateColumnRenamesToDistinctActions($schemaDiff);
 
         if ($renameUnused) {
             $schemaDiff = $this->migrateUnprefixedRemovedTablesToRenames($schemaDiff);
@@ -592,7 +593,7 @@ class ConnectionMigrator
             // Treat renamed indexes as a field change as it's a simple rename operation
             if (count($changedTable->renamedIndexes) !== 0) {
                 // Create a base table diff without any changes, there's no constructor
-                // argument to pass in renamed columns.
+                // argument to pass in renamed indexes.
                 $tableDiff = GeneralUtility::makeInstance(
                     TableDiff::class,
                     $changedTable->getName($this->connection->getDatabasePlatform()),
@@ -917,7 +918,11 @@ class ConnectionMigrator
                 $fromTable = $removedTable
             );
 
-            $tableDiff->newName = substr($this->deletedPrefix . $removedTable->getName(), 0, $this->getMaxTableNameLength());
+            $tableDiff->newName = substr(
+                $this->deletedPrefix . $removedTable->getName(),
+                0,
+                $this->getMaxTableNameLength()
+            );
             $schemaDiff->changedTables[$index] = $tableDiff;
             unset($schemaDiff->removedTables[$index]);
         }
@@ -947,7 +952,11 @@ class ConnectionMigrator
                 }
 
                 // Build a new column object with the same properties as the removed column
-                $renamedColumnName = substr($this->deletedPrefix . $removedColumn->getName(), 0, $this->getMaxColumnNameLength());
+                $renamedColumnName = substr(
+                    $this->deletedPrefix . $removedColumn->getName(),
+                    0,
+                    $this->getMaxColumnNameLength()
+                );
                 $renamedColumn = new Column(
                     $this->connection->quoteIdentifier($renamedColumnName),
                     $removedColumn->getType(),
@@ -975,6 +984,47 @@ class ConnectionMigrator
     }
 
     /**
+     * Revert the automatic rename optimization that Doctrine performs when it detects
+     * a column being added and a column being dropped that only differ by name.
+     *
+     * @param \Doctrine\DBAL\Schema\SchemaDiff $schemaDiff
+     * @return SchemaDiff
+     * @throws \Doctrine\DBAL\Schema\SchemaException
+     * @throws \InvalidArgumentException
+     */
+    protected function migrateColumnRenamesToDistinctActions(SchemaDiff $schemaDiff): SchemaDiff
+    {
+        foreach ($schemaDiff->changedTables as $index => $changedTable) {
+            if (count($changedTable->renamedColumns) === 0) {
+                continue;
+            }
+
+            // Treat each renamed column with a new diff to get a dedicated
+            // suggestion just for this single column.
+            foreach ($changedTable->renamedColumns as $originalColumnName => $renamedColumn) {
+                $columnOptions = array_diff_key($renamedColumn->toArray(), ['name', 'type']);
+
+                $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance(
+                    Column::class,
+                    $renamedColumn->getName(),
+                    $renamedColumn->getType(),
+                    $columnOptions
+                );
+                $changedTable->removedColumns[$originalColumnName] = GeneralUtility::makeInstance(
+                    Column::class,
+                    $originalColumnName,
+                    $renamedColumn->getType(),
+                    $columnOptions
+                );
+
+                unset($changedTable->renamedColumns[$originalColumnName]);
+            }
+        }
+
+        return $schemaDiff;
+    }
+
+    /**
      * Retrieve the database platform-specific limitations on column and schema name sizes as
      * defined in the tableAndFieldMaxNameLengthsPerDbPlatform property.
      *