TYPO3 CMS  TYPO3_8-7
ConnectionMigrator.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
33 
40 {
44  protected $deletedPrefix = 'zzz_deleted_';
45 
49  protected $connection;
50 
54  protected $connectionName;
55 
59  protected $tables;
60 
65  public function __construct(string $connectionName, array $tables)
66  {
67  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
68  $this->connection = $connectionPool->getConnectionByName($connectionName);
69  $this->connectionName = $connectionName;
70  $this->tables = $tables;
71  }
72 
78  public static function create(string $connectionName, array $tables)
79  {
81  static::class,
82  $connectionName,
83  $tables
84  );
85  }
86 
93  public function getSchemaDiff(): SchemaDiff
94  {
95  return $this->buildSchemaDiff(false);
96  }
97 
105  public function getUpdateSuggestions(bool $remove = false): array
106  {
107  $schemaDiff = $this->buildSchemaDiff();
108 
109  if ($remove === false) {
110  return array_merge_recursive(
111  ['add' => [], 'create_table' => [], 'change' => [], 'change_currentValue' => []],
112  $this->getNewFieldUpdateSuggestions($schemaDiff),
113  $this->getNewTableUpdateSuggestions($schemaDiff),
114  $this->getChangedFieldUpdateSuggestions($schemaDiff),
115  $this->getChangedTableOptions($schemaDiff)
116  );
117  }
118  return array_merge_recursive(
119  ['change' => [], 'change_table' => [], 'drop' => [], 'drop_table' => [], 'tables_count' => []],
120  $this->getUnusedFieldUpdateSuggestions($schemaDiff),
121  $this->getUnusedTableUpdateSuggestions($schemaDiff),
122  $this->getDropTableUpdateSuggestions($schemaDiff),
123  $this->getDropFieldUpdateSuggestions($schemaDiff)
124  );
125  }
126 
135  public function install(bool $createOnly = false): array
136  {
137  $result = [];
138  $schemaDiff = $this->buildSchemaDiff(false);
139 
140  $schemaDiff->removedTables = [];
141  foreach ($schemaDiff->changedTables as $key => $changedTable) {
142  $schemaDiff->changedTables[$key]->removedColumns = [];
143  $schemaDiff->changedTables[$key]->removedIndexes = [];
144 
145  // With partial ext_tables.sql files the SchemaManager is detecting
146  // existing columns as false positives for a column rename. In this
147  // context every rename is actually a new column.
148  foreach ($changedTable->renamedColumns as $columnName => $renamedColumn) {
149  $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance(
150  Column::class,
151  $renamedColumn->getName(),
152  $renamedColumn->getType(),
153  array_diff_key($renamedColumn->toArray(), ['name', 'type'])
154  );
155  unset($changedTable->renamedColumns[$columnName]);
156  }
157 
158  if ($createOnly) {
159  // Ignore new indexes that work on columns that need changes
160  foreach ($changedTable->addedIndexes as $indexName => $addedIndex) {
161  $indexColumns = array_map(
162  function ($columnName) {
163  // Strip MySQL prefix length information to get real column names
164  $columnName = preg_replace('/\(\d+\)$/', '', $columnName);
165  // Strip mssql '[' and ']' from column names
166  $columnName = ltrim($columnName, '[');
167  return rtrim($columnName, ']');
168  },
169  $addedIndex->getColumns()
170  );
171  $columnChanges = array_intersect($indexColumns, array_keys($changedTable->changedColumns));
172  if (!empty($columnChanges)) {
173  unset($schemaDiff->changedTables[$key]->addedIndexes[$indexName]);
174  }
175  }
176  $schemaDiff->changedTables[$key]->changedColumns = [];
177  $schemaDiff->changedTables[$key]->changedIndexes = [];
178  $schemaDiff->changedTables[$key]->renamedIndexes = [];
179  }
180  }
181 
182  $statements = $schemaDiff->toSaveSql(
183  $this->connection->getDatabasePlatform()
184  );
185 
186  foreach ($statements as $statement) {
187  try {
188  $this->connection->executeUpdate($statement);
189  $result[$statement] = '';
190  } catch (DBALException $e) {
191  $result[$statement] = $e->getPrevious()->getMessage();
192  }
193  }
194 
195  return $result;
196  }
197 
209  protected function buildSchemaDiff(bool $renameUnused = true): SchemaDiff
210  {
211  // Build the schema definitions
212  $fromSchema = $this->connection->getSchemaManager()->createSchema();
213  $toSchema = $this->buildExpectedSchemaDefinitions($this->connectionName);
214 
215  // Add current table options to the fromSchema
216  $tableOptions = $this->getTableOptions($fromSchema->getTableNames());
217  foreach ($fromSchema->getTables() as $table) {
218  $tableName = $table->getName();
219  if (!array_key_exists($tableName, $tableOptions)) {
220  continue;
221  }
222  foreach ($tableOptions[$tableName] as $optionName => $optionValue) {
223  $table->addOption($optionName, $optionValue);
224  }
225  }
226 
227  // Build SchemaDiff and handle renames of tables and colums
228  $comparator = GeneralUtility::makeInstance(Comparator::class, $this->connection->getDatabasePlatform());
229  $schemaDiff = $comparator->compare($fromSchema, $toSchema);
230  $schemaDiff = $this->migrateColumnRenamesToDistinctActions($schemaDiff);
231 
232  if ($renameUnused) {
233  $schemaDiff = $this->migrateUnprefixedRemovedTablesToRenames($schemaDiff);
234  $schemaDiff = $this->migrateUnprefixedRemovedFieldsToRenames($schemaDiff);
235  }
236 
237  // All tables in the default connection are managed by TYPO3
238  if ($this->connectionName === ConnectionPool::DEFAULT_CONNECTION_NAME) {
239  return $schemaDiff;
240  }
241 
242  // If there are no mapped tables return a SchemaDiff without any changes
243  // to avoid update suggestions for tables not related to TYPO3.
244  if (empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])
245  || !is_array($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])
246  ) {
248  $schemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, [], [], [], $fromSchema);
249 
250  return $schemaDiff;
251  }
252 
253  // Collect the table names that have been mapped to this connection.
255  $tablesForConnection = array_keys(
256  array_filter(
257  $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'],
258  function ($tableConnectionName) use ($connectionName) {
259  return $tableConnectionName === $connectionName;
260  }
261  )
262  );
263 
264  // Remove all tables that are not assigned to this connection from the diff
265  $schemaDiff->newTables = $this->removeUnrelatedTables($schemaDiff->newTables, $tablesForConnection);
266  $schemaDiff->changedTables = $this->removeUnrelatedTables($schemaDiff->changedTables, $tablesForConnection);
267  $schemaDiff->removedTables = $this->removeUnrelatedTables($schemaDiff->removedTables, $tablesForConnection);
268 
269  return $schemaDiff;
270  }
271 
280  protected function buildExpectedSchemaDefinitions(string $connectionName): Schema
281  {
283  $tablesForConnection = [];
284  foreach ($this->tables as $table) {
285  $tableName = $table->getName();
286 
287  // Skip tables for a different connection
288  if ($connectionName !== $this->getConnectionNameForTable($tableName)) {
289  continue;
290  }
291 
292  if (!array_key_exists($tableName, $tablesForConnection)) {
293  $tablesForConnection[$tableName] = $table;
294  continue;
295  }
296 
297  // Merge multiple table definitions. Later definitions overrule identical
298  // columns, indexes and foreign_keys. Order of definitions is based on
299  // extension load order.
300  $currentTableDefinition = $tablesForConnection[$tableName];
301  $tablesForConnection[$tableName] = GeneralUtility::makeInstance(
302  Table::class,
303  $tableName,
304  array_merge($currentTableDefinition->getColumns(), $table->getColumns()),
305  array_merge($currentTableDefinition->getIndexes(), $table->getIndexes()),
306  array_merge($currentTableDefinition->getForeignKeys(), $table->getForeignKeys()),
307  0,
308  array_merge($currentTableDefinition->getOptions(), $table->getOptions())
309  );
310  }
311 
312  $tablesForConnection = $this->transformTablesForDatabasePlatform($tablesForConnection, $this->connection);
313 
314  $schemaConfig = GeneralUtility::makeInstance(SchemaConfig::class);
315  $schemaConfig->setName($this->connection->getDatabase());
316 
317  return GeneralUtility::makeInstance(Schema::class, $tablesForConnection, [], $schemaConfig);
318  }
319 
328  protected function getNewTableUpdateSuggestions(SchemaDiff $schemaDiff): array
329  {
330  // Build a new schema diff that only contains added tables
331  $addTableSchemaDiff = GeneralUtility::makeInstance(
332  SchemaDiff::class,
333  $schemaDiff->newTables,
334  [],
335  [],
336  $schemaDiff->fromSchema
337  );
338 
339  $statements = $addTableSchemaDiff->toSql($this->connection->getDatabasePlatform());
340 
341  return ['create_table' => $this->calculateUpdateSuggestionsHashes($statements)];
342  }
343 
353  protected function getNewFieldUpdateSuggestions(SchemaDiff $schemaDiff): array
354  {
355  $changedTables = [];
356 
357  foreach ($schemaDiff->changedTables as $index => $changedTable) {
358  $fromTable = $this->buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name));
359 
360  if (count($changedTable->addedColumns) !== 0) {
361  // Treat each added column with a new diff to get a dedicated suggestions
362  // just for this single column.
363  foreach ($changedTable->addedColumns as $addedColumn) {
364  $changedTables[$index . ':tbl_' . $addedColumn->getName()] = GeneralUtility::makeInstance(
365  TableDiff::class,
366  $changedTable->name,
367  [$addedColumn],
368  [],
369  [],
370  [],
371  [],
372  [],
373  $fromTable
374  );
375  }
376  }
377 
378  if (count($changedTable->addedIndexes) !== 0) {
379  // Treat each added index with a new diff to get a dedicated suggestions
380  // just for this index.
381  foreach ($changedTable->addedIndexes as $addedIndex) {
382  $changedTables[$index . ':idx_' . $addedIndex->getName()] = GeneralUtility::makeInstance(
383  TableDiff::class,
384  $changedTable->name,
385  [],
386  [],
387  [],
388  [$this->buildQuotedIndex($addedIndex)],
389  [],
390  [],
391  $fromTable
392  );
393  }
394  }
395 
396  if (count($changedTable->addedForeignKeys) !== 0) {
397  // Treat each added foreign key with a new diff to get a dedicated suggestions
398  // just for this foreign key.
399  foreach ($changedTable->addedForeignKeys as $addedForeignKey) {
400  $fkIndex = $index . ':fk_' . $addedForeignKey->getName();
401  $changedTables[$fkIndex] = GeneralUtility::makeInstance(
402  TableDiff::class,
403  $changedTable->name,
404  [],
405  [],
406  [],
407  [],
408  [],
409  [],
410  $fromTable
411  );
412  $changedTables[$fkIndex]->addedForeignKeys = [$this->buildQuotedForeignKey($addedForeignKey)];
413  }
414  }
415  }
416 
417  // Build a new schema diff that only contains added fields
418  $addFieldSchemaDiff = GeneralUtility::makeInstance(
419  SchemaDiff::class,
420  [],
421  $changedTables,
422  [],
423  $schemaDiff->fromSchema
424  );
425 
426  $statements = $addFieldSchemaDiff->toSql($this->connection->getDatabasePlatform());
427 
428  return ['add' => $this->calculateUpdateSuggestionsHashes($statements)];
429  }
430 
440  protected function getChangedTableOptions(SchemaDiff $schemaDiff): array
441  {
442  $updateSuggestions = [];
443 
444  foreach ($schemaDiff->changedTables as $tableDiff) {
445  // Skip processing if this is the base TableDiff class or has no table options set.
446  if (!$tableDiff instanceof TableDiff || count($tableDiff->getTableOptions()) === 0) {
447  continue;
448  }
449 
450  $tableOptions = $tableDiff->getTableOptions();
451  $tableOptionsDiff = GeneralUtility::makeInstance(
452  TableDiff::class,
453  $tableDiff->name,
454  [],
455  [],
456  [],
457  [],
458  [],
459  [],
460  $tableDiff->fromTable
461  );
462  $tableOptionsDiff->setTableOptions($tableOptions);
463 
464  $tableOptionsSchemaDiff = GeneralUtility::makeInstance(
465  SchemaDiff::class,
466  [],
467  [$tableOptionsDiff],
468  [],
469  $schemaDiff->fromSchema
470  );
471 
472  $statements = $tableOptionsSchemaDiff->toSaveSql($this->connection->getDatabasePlatform());
473  foreach ($statements as $statement) {
474  $updateSuggestions['change'][md5($statement)] = $statement;
475  }
476  }
477 
478  return $updateSuggestions;
479  }
480 
490  protected function getChangedFieldUpdateSuggestions(SchemaDiff $schemaDiff): array
491  {
492  $databasePlatform = $this->connection->getDatabasePlatform();
493  $updateSuggestions = [];
494 
495  foreach ($schemaDiff->changedTables as $index => $changedTable) {
496  if (count($changedTable->changedColumns) !== 0) {
497  // Treat each changed column with a new diff to get a dedicated suggestions
498  // just for this single column.
499  $fromTable = $this->buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name));
500 
501  foreach ($changedTable->changedColumns as $changedColumn) {
502  // Field has been renamed and will be handled separately
503  if ($changedColumn->getOldColumnName()->getName() !== $changedColumn->column->getName()) {
504  continue;
505  }
506 
507  $changedColumn->fromColumn = $this->buildQuotedColumn($changedColumn->fromColumn);
508 
509  // Get the current SQL declaration for the column
510  $currentColumn = $fromTable->getColumn($changedColumn->getOldColumnName()->getName());
511  $currentDeclaration = $databasePlatform->getColumnDeclarationSQL(
512  $currentColumn->getQuotedName($this->connection->getDatabasePlatform()),
513  $currentColumn->toArray()
514  );
515 
516  // Build a dedicated diff just for the current column
517  $tableDiff = GeneralUtility::makeInstance(
518  TableDiff::class,
519  $changedTable->name,
520  [],
521  [$changedColumn],
522  [],
523  [],
524  [],
525  [],
526  $fromTable
527  );
528 
529  $temporarySchemaDiff = GeneralUtility::makeInstance(
530  SchemaDiff::class,
531  [],
532  [$tableDiff],
533  [],
534  $schemaDiff->fromSchema
535  );
536 
537  $statements = $temporarySchemaDiff->toSql($databasePlatform);
538  foreach ($statements as $statement) {
539  $updateSuggestions['change'][md5($statement)] = $statement;
540  $updateSuggestions['change_currentValue'][md5($statement)] = $currentDeclaration;
541  }
542  }
543  }
544 
545  // Treat each changed index with a new diff to get a dedicated suggestions
546  // just for this index.
547  if (count($changedTable->changedIndexes) !== 0) {
548  foreach ($changedTable->renamedIndexes as $key => $changedIndex) {
549  $indexDiff = GeneralUtility::makeInstance(
550  TableDiff::class,
551  $changedTable->name,
552  [],
553  [],
554  [],
555  [],
556  [$changedIndex],
557  [],
558  $schemaDiff->fromSchema->getTable($changedTable->name)
559  );
560 
561  $temporarySchemaDiff = GeneralUtility::makeInstance(
562  SchemaDiff::class,
563  [],
564  [$indexDiff],
565  [],
566  $schemaDiff->fromSchema
567  );
568 
569  $statements = $temporarySchemaDiff->toSql($databasePlatform);
570  foreach ($statements as $statement) {
571  $updateSuggestions['change'][md5($statement)] = $statement;
572  }
573  }
574  }
575 
576  // Treat renamed indexes as a field change as it's a simple rename operation
577  if (count($changedTable->renamedIndexes) !== 0) {
578  // Create a base table diff without any changes, there's no constructor
579  // argument to pass in renamed indexes.
580  $tableDiff = GeneralUtility::makeInstance(
581  TableDiff::class,
582  $changedTable->name,
583  [],
584  [],
585  [],
586  [],
587  [],
588  [],
589  $schemaDiff->fromSchema->getTable($changedTable->name)
590  );
591 
592  // Treat each renamed index with a new diff to get a dedicated suggestions
593  // just for this index.
594  foreach ($changedTable->renamedIndexes as $key => $renamedIndex) {
595  $indexDiff = clone $tableDiff;
596  $indexDiff->renamedIndexes = [$key => $renamedIndex];
597 
598  $temporarySchemaDiff = GeneralUtility::makeInstance(
599  SchemaDiff::class,
600  [],
601  [$indexDiff],
602  [],
603  $schemaDiff->fromSchema
604  );
605 
606  $statements = $temporarySchemaDiff->toSql($databasePlatform);
607  foreach ($statements as $statement) {
608  $updateSuggestions['change'][md5($statement)] = $statement;
609  }
610  }
611  }
612 
613  // Treat each changed foreign key with a new diff to get a dedicated suggestions
614  // just for this foreign key.
615  if (count($changedTable->changedForeignKeys) !== 0) {
616  $tableDiff = GeneralUtility::makeInstance(
617  TableDiff::class,
618  $changedTable->name,
619  [],
620  [],
621  [],
622  [],
623  [],
624  [],
625  $schemaDiff->fromSchema->getTable($changedTable->name)
626  );
627 
628  foreach ($changedTable->changedForeignKeys as $changedForeignKey) {
629  $foreignKeyDiff = clone $tableDiff;
630  $foreignKeyDiff->changedForeignKeys = [$this->buildQuotedForeignKey($changedForeignKey)];
631 
632  $temporarySchemaDiff = GeneralUtility::makeInstance(
633  SchemaDiff::class,
634  [],
635  [$foreignKeyDiff],
636  [],
637  $schemaDiff->fromSchema
638  );
639 
640  $statements = $temporarySchemaDiff->toSql($databasePlatform);
641  foreach ($statements as $statement) {
642  $updateSuggestions['change'][md5($statement)] = $statement;
643  }
644  }
645  }
646  }
647 
648  return $updateSuggestions;
649  }
650 
662  protected function getUnusedTableUpdateSuggestions(SchemaDiff $schemaDiff): array
663  {
664  $updateSuggestions = [];
665  foreach ($schemaDiff->changedTables as $tableDiff) {
666  // Skip tables that are not being renamed or where the new name isn't prefixed
667  // with the deletion marker.
668  if ($tableDiff->getNewName() === false
669  || strpos($tableDiff->getNewName()->getName(), $this->deletedPrefix) !== 0
670  ) {
671  continue;
672  }
673  // Build a new schema diff that only contains this table
674  $changedFieldDiff = GeneralUtility::makeInstance(
675  SchemaDiff::class,
676  [],
677  [$tableDiff],
678  [],
679  $schemaDiff->fromSchema
680  );
681 
682  $statements = $changedFieldDiff->toSql($this->connection->getDatabasePlatform());
683 
684  foreach ($statements as $statement) {
685  $updateSuggestions['change_table'][md5($statement)] = $statement;
686  }
687  $updateSuggestions['tables_count'][md5($statements[0])] = $this->getTableRecordCount((string)$tableDiff->name);
688  }
689 
690  return $updateSuggestions;
691  }
692 
704  protected function getUnusedFieldUpdateSuggestions(SchemaDiff $schemaDiff): array
705  {
706  $changedTables = [];
707 
708  foreach ($schemaDiff->changedTables as $index => $changedTable) {
709  if (count($changedTable->changedColumns) === 0) {
710  continue;
711  }
712 
713  // Treat each changed column with a new diff to get a dedicated suggestions
714  // just for this single column.
715  foreach ($changedTable->changedColumns as $changedColumn) {
716  // Field has not been renamed
717  if ($changedColumn->getOldColumnName()->getName() === $changedColumn->column->getName()) {
718  continue;
719  }
720 
721  $changedTables[$index . ':' . $changedColumn->column->getName()] = GeneralUtility::makeInstance(
722  TableDiff::class,
723  $changedTable->name,
724  [],
725  [$changedColumn],
726  [],
727  [],
728  [],
729  [],
730  $this->buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name))
731  );
732  }
733  }
734 
735  // Build a new schema diff that only contains unused fields
736  $changedFieldDiff = GeneralUtility::makeInstance(
737  SchemaDiff::class,
738  [],
739  $changedTables,
740  [],
741  $schemaDiff->fromSchema
742  );
743 
744  $statements = $changedFieldDiff->toSql($this->connection->getDatabasePlatform());
745 
746  return ['change' => $this->calculateUpdateSuggestionsHashes($statements)];
747  }
748 
760  protected function getDropFieldUpdateSuggestions(SchemaDiff $schemaDiff): array
761  {
762  $changedTables = [];
763 
764  foreach ($schemaDiff->changedTables as $index => $changedTable) {
765  $fromTable = $this->buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name));
766 
767  if (count($changedTable->removedColumns) !== 0) {
768  // Treat each changed column with a new diff to get a dedicated suggestions
769  // just for this single column.
770  foreach ($changedTable->removedColumns as $removedColumn) {
771  $changedTables[$index . ':tbl_' . $removedColumn->getName()] = GeneralUtility::makeInstance(
772  TableDiff::class,
773  $changedTable->name,
774  [],
775  [],
776  [$this->buildQuotedColumn($removedColumn)],
777  [],
778  [],
779  [],
780  $fromTable
781  );
782  }
783  }
784 
785  if (count($changedTable->removedIndexes) !== 0) {
786  // Treat each removed index with a new diff to get a dedicated suggestions
787  // just for this index.
788  foreach ($changedTable->removedIndexes as $removedIndex) {
789  $changedTables[$index . ':idx_' . $removedIndex->getName()] = GeneralUtility::makeInstance(
790  TableDiff::class,
791  $changedTable->name,
792  [],
793  [],
794  [],
795  [],
796  [],
797  [$this->buildQuotedIndex($removedIndex)],
798  $fromTable
799  );
800  }
801  }
802 
803  if (count($changedTable->removedForeignKeys) !== 0) {
804  // Treat each removed foreign key with a new diff to get a dedicated suggestions
805  // just for this foreign key.
806  foreach ($changedTable->removedForeignKeys as $removedForeignKey) {
807  $fkIndex = $index . ':fk_' . $removedForeignKey->getName();
808  $changedTables[$fkIndex] = GeneralUtility::makeInstance(
809  TableDiff::class,
810  $changedTable->name,
811  [],
812  [],
813  [],
814  [],
815  [],
816  [],
817  $fromTable
818  );
819  $changedTables[$fkIndex]->removedForeignKeys = [$this->buildQuotedForeignKey($removedForeignKey)];
820  }
821  }
822  }
823 
824  // Build a new schema diff that only contains removable fields
825  $removedFieldDiff = GeneralUtility::makeInstance(
826  SchemaDiff::class,
827  [],
828  $changedTables,
829  [],
830  $schemaDiff->fromSchema
831  );
832 
833  $statements = $removedFieldDiff->toSql($this->connection->getDatabasePlatform());
834 
835  return ['drop' => $this->calculateUpdateSuggestionsHashes($statements)];
836  }
837 
849  protected function getDropTableUpdateSuggestions(SchemaDiff $schemaDiff): array
850  {
851  $updateSuggestions = [];
852  foreach ($schemaDiff->removedTables as $removedTable) {
853  // Build a new schema diff that only contains this table
854  $tableDiff = GeneralUtility::makeInstance(
855  SchemaDiff::class,
856  [],
857  [],
858  [$this->buildQuotedTable($removedTable)],
859  $schemaDiff->fromSchema
860  );
861 
862  $statements = $tableDiff->toSql($this->connection->getDatabasePlatform());
863  foreach ($statements as $statement) {
864  $updateSuggestions['drop_table'][md5($statement)] = $statement;
865  }
866 
867  // Only store the record count for this table for the first statement,
868  // assuming that this is the actual DROP TABLE statement.
869  $updateSuggestions['tables_count'][md5($statements[0])] = $this->getTableRecordCount(
870  $removedTable->getName()
871  );
872  }
873 
874  return $updateSuggestions;
875  }
876 
888  protected function migrateUnprefixedRemovedTablesToRenames(SchemaDiff $schemaDiff): SchemaDiff
889  {
890  foreach ($schemaDiff->removedTables as $index => $removedTable) {
891  if (strpos($removedTable->getName(), $this->deletedPrefix) === 0) {
892  continue;
893  }
894  $tableDiff = GeneralUtility::makeInstance(
895  TableDiff::class,
896  $removedTable->getQuotedName($this->connection->getDatabasePlatform()),
897  $addedColumns = [],
898  $changedColumns = [],
899  $removedColumns = [],
900  $addedIndexes = [],
901  $changedIndexes = [],
902  $removedIndexes = [],
903  $this->buildQuotedTable($removedTable)
904  );
905 
906  $tableDiff->newName = $this->connection->getDatabasePlatform()->quoteIdentifier(
907  substr(
908  $this->deletedPrefix . $removedTable->getName(),
909  0,
910  PlatformInformation::getMaxIdentifierLength($this->connection->getDatabasePlatform())
911  )
912  );
913  $schemaDiff->changedTables[$index] = $tableDiff;
914  unset($schemaDiff->removedTables[$index]);
915  }
916 
917  return $schemaDiff;
918  }
919 
929  protected function migrateUnprefixedRemovedFieldsToRenames(SchemaDiff $schemaDiff): SchemaDiff
930  {
931  foreach ($schemaDiff->changedTables as $tableIndex => $changedTable) {
932  if (count($changedTable->removedColumns) === 0) {
933  continue;
934  }
935 
936  foreach ($changedTable->removedColumns as $columnIndex => $removedColumn) {
937  if (strpos($removedColumn->getName(), $this->deletedPrefix) === 0) {
938  continue;
939  }
940 
941  // Build a new column object with the same properties as the removed column
942  $renamedColumnName = substr(
943  $this->deletedPrefix . $removedColumn->getName(),
944  0,
945  PlatformInformation::getMaxIdentifierLength($this->connection->getDatabasePlatform())
946  );
947  $renamedColumn = new Column(
948  $this->connection->quoteIdentifier($renamedColumnName),
949  $removedColumn->getType(),
950  array_diff_key($removedColumn->toArray(), ['name', 'type'])
951  );
952 
953  // Build the diff object for the column to rename
954  $columnDiff = GeneralUtility::makeInstance(
955  ColumnDiff::class,
956  $removedColumn->getQuotedName($this->connection->getDatabasePlatform()),
957  $renamedColumn,
958  $changedProperties = [],
959  $this->buildQuotedColumn($removedColumn)
960  );
961 
962  // Add the column with the required rename information to the changed column list
963  $schemaDiff->changedTables[$tableIndex]->changedColumns[$columnIndex] = $columnDiff;
964 
965  // Remove the column from the list of columns to be dropped
966  unset($schemaDiff->changedTables[$tableIndex]->removedColumns[$columnIndex]);
967  }
968  }
969 
970  return $schemaDiff;
971  }
972 
982  protected function migrateColumnRenamesToDistinctActions(SchemaDiff $schemaDiff): SchemaDiff
983  {
984  foreach ($schemaDiff->changedTables as $index => $changedTable) {
985  if (count($changedTable->renamedColumns) === 0) {
986  continue;
987  }
988 
989  // Treat each renamed column with a new diff to get a dedicated
990  // suggestion just for this single column.
991  foreach ($changedTable->renamedColumns as $originalColumnName => $renamedColumn) {
992  $columnOptions = array_diff_key($renamedColumn->toArray(), ['name', 'type']);
993 
994  $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance(
995  Column::class,
996  $renamedColumn->getName(),
997  $renamedColumn->getType(),
998  $columnOptions
999  );
1000  $changedTable->removedColumns[$originalColumnName] = GeneralUtility::makeInstance(
1001  Column::class,
1002  $originalColumnName,
1003  $renamedColumn->getType(),
1004  $columnOptions
1005  );
1006 
1007  unset($changedTable->renamedColumns[$originalColumnName]);
1008  }
1009  }
1010 
1011  return $schemaDiff;
1012  }
1013 
1021  protected function getTableRecordCount(string $tableName): int
1022  {
1023  return GeneralUtility::makeInstance(ConnectionPool::class)
1024  ->getConnectionForTable($tableName)
1025  ->count('*', $tableName, []);
1026  }
1027 
1035  protected function getConnectionNameForTable(string $tableName): string
1036  {
1037  $connectionNames = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionNames();
1038 
1039  if (array_key_exists($tableName, (array)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])) {
1040  return in_array($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName], $connectionNames, true)
1041  ? $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName]
1043  }
1044 
1046  }
1047 
1054  protected function calculateUpdateSuggestionsHashes(array $statements): array
1055  {
1056  return array_combine(array_map('md5', $statements), $statements);
1057  }
1058 
1067  protected function removeUnrelatedTables(array $tableDiffs, array $validTableNames): array
1068  {
1069  return array_filter(
1070  $tableDiffs,
1071  function ($table) use ($validTableNames) {
1072  if ($table instanceof Table) {
1073  $tableName = $table->getName();
1074  } else {
1075  $tableName = $table->newName ?: $table->name;
1076  }
1077 
1078  // If the tablename has a deleted prefix strip it of before comparing
1079  // it against the list of valid table names so that drop operations
1080  // don't get removed.
1081  if (strpos($tableName, $this->deletedPrefix) === 0) {
1082  $tableName = substr($tableName, strlen($this->deletedPrefix));
1083  }
1084  return in_array($tableName, $validTableNames, true)
1085  || in_array($this->deletedPrefix . $tableName, $validTableNames, true);
1086  }
1087  );
1088  }
1089 
1101  {
1102  foreach ($tables as &$table) {
1103  $indexes = [];
1104  foreach ($table->getIndexes() as $key => $index) {
1105  $indexName = $index->getName();
1106  // PostgreSQL requires index names to be unique per database/schema.
1107  if ($connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
1108  $indexName = $indexName . '_' . hash('crc32b', $table->getName() . '_' . $indexName);
1109  }
1110 
1111  // Remove the length information from column names for indexes if required.
1112  $cleanedColumnNames = array_map(
1113  function (string $columnName) use ($connection) {
1114  if ($connection->getDatabasePlatform() instanceof MySqlPlatform) {
1115  // Returning the unquoted, unmodified version of the column name since
1116  // it can include the length information for BLOB/TEXT columns which
1117  // may not be quoted.
1118  return $columnName;
1119  }
1120 
1121  return $connection->quoteIdentifier(preg_replace('/\(\d+\)$/', '', $columnName));
1122  },
1123  $index->getUnquotedColumns()
1124  );
1125 
1126  $indexes[$key] = GeneralUtility::makeInstance(
1127  Index::class,
1128  $connection->quoteIdentifier($indexName),
1129  $cleanedColumnNames,
1130  $index->isUnique(),
1131  $index->isPrimary(),
1132  $index->getFlags(),
1133  $index->getOptions()
1134  );
1135  }
1136 
1138  Table::class,
1139  $table->getQuotedName($connection->getDatabasePlatform()),
1140  $table->getColumns(),
1141  $indexes,
1142  $table->getForeignKeys(),
1143  0,
1144  $table->getOptions()
1145  );
1146  }
1147 
1148  return $tables;
1149  }
1150 
1158  protected function getTableOptions(array $tableNames): array
1159  {
1160  $tableOptions = [];
1161  if (strpos($this->connection->getServerVersion(), 'MySQL') !== 0) {
1162  foreach ($tableNames as $tableName) {
1163  $tableOptions[$tableName] = [];
1164  }
1165 
1166  return $tableOptions;
1167  }
1168 
1169  $queryBuilder = $this->connection->createQueryBuilder();
1170  $result = $queryBuilder
1171  ->select(
1172  'TABLE_NAME AS table',
1173  'ENGINE AS engine',
1174  'ROW_FORMAT AS row_format',
1175  'TABLE_COLLATION AS collate',
1176  'TABLE_COMMENT AS comment'
1177  )
1178  ->from('information_schema.TABLES')
1179  ->where(
1180  $queryBuilder->expr()->eq(
1181  'TABLE_TYPE',
1182  $queryBuilder->createNamedParameter('BASE TABLE', \PDO::PARAM_STR)
1183  ),
1184  $queryBuilder->expr()->eq(
1185  'TABLE_SCHEMA',
1186  $queryBuilder->createNamedParameter($this->connection->getDatabase(), \PDO::PARAM_STR)
1187  )
1188  )
1189  ->execute();
1190 
1191  while ($row = $result->fetch()) {
1192  $index = $row['table'];
1193  unset($row['table']);
1194  $tableOptions[$index] = $row;
1195  }
1196 
1197  return $tableOptions;
1198  }
1199 
1209  protected function buildQuotedTable(Table $table): Table
1210  {
1211  $databasePlatform = $this->connection->getDatabasePlatform();
1212 
1214  Table::class,
1215  $databasePlatform->quoteIdentifier($table->getName()),
1216  $table->getColumns(),
1217  $table->getIndexes(),
1218  $table->getForeignKeys(),
1219  0,
1220  $table->getOptions()
1221  );
1222  }
1223 
1233  protected function buildQuotedColumn(Column $column): Column
1234  {
1235  $databasePlatform = $this->connection->getDatabasePlatform();
1236 
1238  Column::class,
1239  $databasePlatform->quoteIdentifier($column->getName()),
1240  $column->getType(),
1241  array_diff_key($column->toArray(), ['name', 'type'])
1242  );
1243  }
1244 
1254  protected function buildQuotedIndex(Index $index): Index
1255  {
1256  $databasePlatform = $this->connection->getDatabasePlatform();
1257 
1259  Index::class,
1260  $databasePlatform->quoteIdentifier($index->getName()),
1261  $index->getColumns(),
1262  $index->isUnique(),
1263  $index->isPrimary(),
1264  $index->getFlags(),
1265  $index->getOptions()
1266  );
1267  }
1268 
1278  protected function buildQuotedForeignKey(ForeignKeyConstraint $index): ForeignKeyConstraint
1279  {
1280  $databasePlatform = $this->connection->getDatabasePlatform();
1281 
1283  ForeignKeyConstraint::class,
1284  $index->getLocalColumns(),
1285  $databasePlatform->quoteIdentifier($index->getForeignTableName()),
1286  $index->getForeignColumns(),
1287  $databasePlatform->quoteIdentifier($index->getName()),
1288  $index->getOptions()
1289  );
1290  }
1291 }
static static getMaxIdentifierLength(AbstractPlatform $platform)
static create(string $connectionName, array $tables)
static makeInstance($className,... $constructorArguments)
transformTablesForDatabasePlatform(array $tables, Connection $connection)
removeUnrelatedTables(array $tableDiffs, array $validTableNames)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
__construct(string $connectionName, array $tables)