2 declare(strict_types = 1);
18 use Doctrine\DBAL\DBALException;
19 use Doctrine\DBAL\Platforms\MySqlPlatform;
20 use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
21 use Doctrine\DBAL\Platforms\SqlitePlatform;
22 use Doctrine\DBAL\Schema\Column;
23 use Doctrine\DBAL\Schema\ColumnDiff;
24 use Doctrine\DBAL\Schema\ForeignKeyConstraint;
25 use Doctrine\DBAL\Schema\Index;
26 use Doctrine\DBAL\Schema\Schema;
27 use Doctrine\DBAL\Schema\SchemaConfig;
28 use Doctrine\DBAL\Schema\SchemaDiff;
29 use Doctrine\DBAL\Schema\Table;
68 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
81 return GeneralUtility::makeInstance(
110 if ($remove ===
false) {
111 return array_merge_recursive(
112 [
'add' => [],
'create_table' => [],
'change' => [],
'change_currentValue' => []],
119 return array_merge_recursive(
120 [
'change' => [],
'change_table' => [],
'drop' => [],
'drop_table' => [],
'tables_count' => []],
136 public function install(
bool $createOnly =
false): array
141 $schemaDiff->removedTables = [];
142 foreach ($schemaDiff->changedTables as $key => $changedTable) {
143 $schemaDiff->changedTables[$key]->removedColumns = [];
144 $schemaDiff->changedTables[$key]->removedIndexes = [];
149 foreach ($changedTable->renamedColumns as $columnName => $renamedColumn) {
150 $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance(
152 $renamedColumn->getName(),
153 $renamedColumn->getType(),
154 array_diff_key($renamedColumn->toArray(), [
'name',
'type'])
156 unset($changedTable->renamedColumns[$columnName]);
161 foreach ($changedTable->addedIndexes as $indexName => $addedIndex) {
162 $indexColumns = array_map(
163 function ($columnName) {
165 $columnName = preg_replace(
'/\(\d+\)$/',
'', $columnName);
167 $columnName = ltrim($columnName,
'[');
168 $columnName = rtrim($columnName,
']');
170 return trim($columnName,
'"');
172 $addedIndex->getColumns()
174 $columnChanges = array_intersect($indexColumns, array_keys($changedTable->changedColumns));
175 if (!empty($columnChanges)) {
176 unset($schemaDiff->changedTables[$key]->addedIndexes[$indexName]);
179 $schemaDiff->changedTables[$key]->changedColumns = [];
180 $schemaDiff->changedTables[$key]->changedIndexes = [];
181 $schemaDiff->changedTables[$key]->renamedIndexes = [];
185 $statements = $schemaDiff->toSaveSql(
186 $this->connection->getDatabasePlatform()
189 foreach ($statements as $statement) {
191 $this->connection->executeUpdate($statement);
192 $result[$statement] =
'';
193 }
catch (DBALException $e) {
194 $result[$statement] = $e->getPrevious()->getMessage();
215 $fromSchema = $this->connection->getSchemaManager()->createSchema();
220 foreach ($fromSchema->getTables() as $table) {
221 $tableName = $table->getName();
222 if (!array_key_exists($tableName, $tableOptions)) {
225 foreach ($tableOptions[$tableName] as $optionName => $optionValue) {
226 $table->addOption($optionName, $optionValue);
231 $comparator = GeneralUtility::makeInstance(Comparator::class, $this->connection->getDatabasePlatform());
232 $schemaDiff = $comparator->compare($fromSchema, $toSchema);
247 if (empty(
$GLOBALS[
'TYPO3_CONF_VARS'][
'DB'][
'TableMapping'] ??
null)) {
248 return GeneralUtility::makeInstance(SchemaDiff::class, [], [], [], $fromSchema);
253 $tablesForConnection = array_keys(
255 $GLOBALS[
'TYPO3_CONF_VARS'][
'DB'][
'TableMapping'],
264 $schemaDiff->changedTables = $this->
removeUnrelatedTables($schemaDiff->changedTables, $tablesForConnection);
265 $schemaDiff->removedTables = $this->
removeUnrelatedTables($schemaDiff->removedTables, $tablesForConnection);
281 $tablesForConnection = [];
282 foreach ($this->tables as $table) {
283 $tableName = $table->getName();
290 if (!array_key_exists($tableName, $tablesForConnection)) {
291 $tablesForConnection[$tableName] = $table;
298 $currentTableDefinition = $tablesForConnection[$tableName];
299 $tablesForConnection[$tableName] = GeneralUtility::makeInstance(
302 array_merge($currentTableDefinition->getColumns(), $table->getColumns()),
303 array_merge($currentTableDefinition->getIndexes(), $table->getIndexes()),
304 array_merge($currentTableDefinition->getForeignKeys(), $table->getForeignKeys()),
306 array_merge($currentTableDefinition->getOptions(), $table->getOptions())
312 $schemaConfig = GeneralUtility::makeInstance(SchemaConfig::class);
313 $schemaConfig->setName($this->connection->getDatabase());
314 if (isset($this->connection->getParams()[
'tableoptions'])) {
315 $schemaConfig->setDefaultTableOptions($this->connection->getParams()[
'tableoptions']);
318 return GeneralUtility::makeInstance(Schema::class, $tablesForConnection, [], $schemaConfig);
332 $addTableSchemaDiff = GeneralUtility::makeInstance(
334 $schemaDiff->newTables,
337 $schemaDiff->fromSchema
340 $statements = $addTableSchemaDiff->toSql($this->connection->getDatabasePlatform());
358 foreach ($schemaDiff->changedTables as $index => $changedTable) {
359 $fromTable = $this->
buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name));
361 if (count($changedTable->addedColumns) !== 0) {
364 foreach ($changedTable->addedColumns as $columnName => $addedColumn) {
365 $changedTables[$index .
':tbl_' . $addedColumn->getName()] = GeneralUtility::makeInstance(
368 [$columnName => $addedColumn],
379 if (count($changedTable->addedIndexes) !== 0) {
382 foreach ($changedTable->addedIndexes as $indexName => $addedIndex) {
383 $changedTables[$index .
':idx_' . $addedIndex->getName()] = GeneralUtility::makeInstance(
389 [$indexName => $this->buildQuotedIndex($addedIndex)],
397 if (count($changedTable->addedForeignKeys) !== 0) {
400 foreach ($changedTable->addedForeignKeys as $addedForeignKey) {
401 $fkIndex = $index .
':fk_' . $addedForeignKey->getName();
402 $changedTables[$fkIndex] = GeneralUtility::makeInstance(
419 $addFieldSchemaDiff = GeneralUtility::makeInstance(
424 $schemaDiff->fromSchema
427 $statements = $addFieldSchemaDiff->toSql($this->connection->getDatabasePlatform());
443 $updateSuggestions = [];
445 foreach ($schemaDiff->changedTables as $tableDiff) {
447 if (!$tableDiff instanceof TableDiff || count($tableDiff->getTableOptions()) === 0) {
451 $tableOptions = $tableDiff->getTableOptions();
452 $tableOptionsDiff = GeneralUtility::makeInstance(
461 $tableDiff->fromTable
463 $tableOptionsDiff->setTableOptions($tableOptions);
465 $tableOptionsSchemaDiff = GeneralUtility::makeInstance(
470 $schemaDiff->fromSchema
473 $statements = $tableOptionsSchemaDiff->toSaveSql($this->connection->getDatabasePlatform());
474 foreach ($statements as $statement) {
475 $updateSuggestions[
'change'][md5($statement)] = $statement;
479 return $updateSuggestions;
493 $databasePlatform = $this->connection->getDatabasePlatform();
494 $updateSuggestions = [];
496 foreach ($schemaDiff->changedTables as $index => $changedTable) {
499 if (count($changedTable->changedIndexes) !== 0) {
500 foreach ($changedTable->changedIndexes as $indexName => $changedIndex) {
501 $indexDiff = GeneralUtility::makeInstance(
508 [$indexName => $changedIndex],
510 $schemaDiff->fromSchema->getTable($changedTable->name)
513 $temporarySchemaDiff = GeneralUtility::makeInstance(
518 $schemaDiff->fromSchema
521 $statements = $temporarySchemaDiff->toSql($databasePlatform);
522 foreach ($statements as $statement) {
523 $updateSuggestions[
'change'][md5($statement)] = $statement;
529 if (count($changedTable->renamedIndexes) !== 0) {
532 $tableDiff = GeneralUtility::makeInstance(
541 $schemaDiff->fromSchema->getTable($changedTable->name)
546 foreach ($changedTable->renamedIndexes as $key => $renamedIndex) {
547 $indexDiff = clone $tableDiff;
548 $indexDiff->renamedIndexes = [$key => $renamedIndex];
550 $temporarySchemaDiff = GeneralUtility::makeInstance(
555 $schemaDiff->fromSchema
558 $statements = $temporarySchemaDiff->toSql($databasePlatform);
559 foreach ($statements as $statement) {
560 $updateSuggestions[
'change'][md5($statement)] = $statement;
565 if (count($changedTable->changedColumns) !== 0) {
568 $fromTable = $this->
buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name));
570 foreach ($changedTable->changedColumns as $columnName => $changedColumn) {
572 if ($changedColumn->getOldColumnName()->getName() !== $changedColumn->column->getName()) {
579 $currentColumn = $fromTable->getColumn($changedColumn->getOldColumnName()->getName());
580 $currentDeclaration = $databasePlatform->getColumnDeclarationSQL(
581 $currentColumn->getQuotedName($this->connection->getDatabasePlatform()),
582 $currentColumn->toArray()
586 $tableDiff = GeneralUtility::makeInstance(
590 [$columnName => $changedColumn],
598 $temporarySchemaDiff = GeneralUtility::makeInstance(
603 $schemaDiff->fromSchema
606 $statements = $temporarySchemaDiff->toSql($databasePlatform);
607 foreach ($statements as $statement) {
608 $updateSuggestions[
'change'][md5($statement)] = $statement;
609 $updateSuggestions[
'change_currentValue'][md5($statement)] = $currentDeclaration;
616 if (count($changedTable->changedForeignKeys) !== 0) {
617 $tableDiff = GeneralUtility::makeInstance(
626 $schemaDiff->fromSchema->getTable($changedTable->name)
629 foreach ($changedTable->changedForeignKeys as $changedForeignKey) {
630 $foreignKeyDiff = clone $tableDiff;
633 $temporarySchemaDiff = GeneralUtility::makeInstance(
638 $schemaDiff->fromSchema
641 $statements = $temporarySchemaDiff->toSql($databasePlatform);
642 foreach ($statements as $statement) {
643 $updateSuggestions[
'change'][md5($statement)] = $statement;
649 return $updateSuggestions;
665 $updateSuggestions = [];
666 foreach ($schemaDiff->changedTables as $tableDiff) {
669 if ($tableDiff->getNewName() ===
false
670 || strpos($tableDiff->getNewName()->getName(), $this->deletedPrefix) !== 0
675 $changedFieldDiff = GeneralUtility::makeInstance(
680 $schemaDiff->fromSchema
683 $statements = $changedFieldDiff->toSql($this->connection->getDatabasePlatform());
685 foreach ($statements as $statement) {
686 $updateSuggestions[
'change_table'][md5($statement)] = $statement;
688 $updateSuggestions[
'tables_count'][md5($statements[0])] = $this->
getTableRecordCount((
string)$tableDiff->name);
691 return $updateSuggestions;
709 foreach ($schemaDiff->changedTables as $index => $changedTable) {
710 if (count($changedTable->changedColumns) === 0) {
718 foreach ($changedTable->changedColumns as $oldFieldName => $changedColumn) {
720 if ($changedColumn->getOldColumnName()->getName() === $changedColumn->column->getName()) {
724 $changedTables[$index .
':' . $changedColumn->column->getName()] = GeneralUtility::makeInstance(
728 [$oldFieldName => $changedColumn],
733 $this->buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name))
742 $changedFieldDiff = GeneralUtility::makeInstance(
747 $schemaDiff->fromSchema
750 $statements = $changedFieldDiff->toSql($this->connection->getDatabasePlatform());
770 foreach ($schemaDiff->changedTables as $index => $changedTable) {
771 $fromTable = $this->
buildQuotedTable($schemaDiff->fromSchema->getTable($changedTable->name));
774 $addMoreOperations =
true;
776 if (count($changedTable->removedColumns) !== 0) {
779 foreach ($changedTable->removedColumns as $columnName => $removedColumn) {
780 $changedTables[$index .
':tbl_' . $removedColumn->getName()] = GeneralUtility::makeInstance(
785 [$columnName => $this->buildQuotedColumn($removedColumn)],
792 $addMoreOperations =
false;
798 if ($addMoreOperations && count($changedTable->removedIndexes) !== 0) {
801 foreach ($changedTable->removedIndexes as $indexName => $removedIndex) {
802 $changedTables[$index .
':idx_' . $removedIndex->getName()] = GeneralUtility::makeInstance(
810 [$indexName => $this->buildQuotedIndex($removedIndex)],
814 $addMoreOperations =
false;
820 if ($addMoreOperations && count($changedTable->removedForeignKeys) !== 0) {
823 foreach ($changedTable->removedForeignKeys as $removedForeignKey) {
824 $fkIndex = $index .
':fk_' . $removedForeignKey->getName();
825 $changedTables[$fkIndex] = GeneralUtility::makeInstance(
845 $removedFieldDiff = GeneralUtility::makeInstance(
850 $schemaDiff->fromSchema
853 $statements = $removedFieldDiff->toSql($this->connection->getDatabasePlatform());
871 $updateSuggestions = [];
872 foreach ($schemaDiff->removedTables as $removedTable) {
874 $tableDiff = GeneralUtility::makeInstance(
879 $schemaDiff->fromSchema
882 $statements = $tableDiff->toSql($this->connection->getDatabasePlatform());
883 foreach ($statements as $statement) {
884 $updateSuggestions[
'drop_table'][md5($statement)] = $statement;
890 $removedTable->getName()
894 return $updateSuggestions;
910 foreach ($schemaDiff->removedTables as $index => $removedTable) {
911 if (strpos($removedTable->getName(), $this->deletedPrefix) === 0) {
914 $tableDiff = GeneralUtility::makeInstance(
916 $removedTable->getQuotedName($this->connection->getDatabasePlatform()),
918 $changedColumns = [],
919 $removedColumns = [],
921 $changedIndexes = [],
922 $removedIndexes = [],
923 $this->buildQuotedTable($removedTable)
926 $tableDiff->newName = $this->connection->getDatabasePlatform()->quoteIdentifier(
928 $this->deletedPrefix . $removedTable->getName(),
933 $schemaDiff->changedTables[$index] = $tableDiff;
934 unset($schemaDiff->removedTables[$index]);
951 foreach ($schemaDiff->changedTables as $tableIndex => $changedTable) {
952 if (count($changedTable->removedColumns) === 0) {
956 foreach ($changedTable->removedColumns as $columnIndex => $removedColumn) {
957 if (strpos($removedColumn->getName(), $this->deletedPrefix) === 0) {
962 $renamedColumnName = substr(
963 $this->deletedPrefix . $removedColumn->getName(),
967 $renamedColumn =
new Column(
968 $this->connection->quoteIdentifier($renamedColumnName),
969 $removedColumn->getType(),
970 array_diff_key($removedColumn->toArray(), [
'name',
'type'])
974 $columnDiff = GeneralUtility::makeInstance(
976 $removedColumn->getQuotedName($this->connection->getDatabasePlatform()),
978 $changedProperties = [],
979 $this->buildQuotedColumn($removedColumn)
983 $schemaDiff->changedTables[$tableIndex]->changedColumns[$columnIndex] = $columnDiff;
986 unset($schemaDiff->changedTables[$tableIndex]->removedColumns[$columnIndex]);
1004 foreach ($schemaDiff->changedTables as $index => $changedTable) {
1005 if (count($changedTable->renamedColumns) === 0) {
1011 foreach ($changedTable->renamedColumns as $originalColumnName => $renamedColumn) {
1012 $columnOptions = array_diff_key($renamedColumn->toArray(), [
'name',
'type']);
1014 $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance(
1016 $renamedColumn->getName(),
1017 $renamedColumn->getType(),
1020 $changedTable->removedColumns[$originalColumnName] = GeneralUtility::makeInstance(
1022 $originalColumnName,
1023 $renamedColumn->getType(),
1027 unset($changedTable->renamedColumns[$originalColumnName]);
1043 return GeneralUtility::makeInstance(ConnectionPool::class)
1044 ->getConnectionForTable($tableName)
1045 ->count(
'*', $tableName, []);
1057 $connectionNames = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionNames();
1059 if (isset(
$GLOBALS[
'TYPO3_CONF_VARS'][
'DB'][
'TableMapping'][$tableName])) {
1060 return in_array(
$GLOBALS[
'TYPO3_CONF_VARS'][
'DB'][
'TableMapping'][$tableName], $connectionNames,
true)
1061 ?
$GLOBALS[
'TYPO3_CONF_VARS'][
'DB'][
'TableMapping'][$tableName]
1076 return array_combine(array_map(
'md5', $statements), $statements);
1089 return array_filter(
1091 function ($table) use ($validTableNames) {
1092 if ($table instanceof Table) {
1093 $tableName = $table->getName();
1095 $tableName = $table->newName ?: $table->name;
1101 if (strpos($tableName, $this->deletedPrefix) === 0) {
1102 $tableName = substr($tableName, strlen($this->deletedPrefix));
1104 return in_array($tableName, $validTableNames,
true)
1105 || in_array($this->deletedPrefix . $tableName, $validTableNames,
true);
1122 $defaultTableOptions =
$connection->getParams()[
'tableoptions'] ?? [];
1125 foreach ($table->getIndexes() as $key => $index) {
1126 $indexName = $index->getName();
1128 if (
$connection->getDatabasePlatform() instanceof PostgreSqlPlatform
1129 ||
$connection->getDatabasePlatform() instanceof SqlitePlatform
1131 $indexName = $indexName .
'_' . hash(
'crc32b', $table->getName() .
'_' . $indexName);
1135 $cleanedColumnNames = array_map(
1137 if (
$connection->getDatabasePlatform() instanceof MySqlPlatform) {
1146 $index->getUnquotedColumns()
1149 $indexes[$key] = GeneralUtility::makeInstance(
1152 $cleanedColumnNames,
1154 $index->isPrimary(),
1156 $index->getOptions()
1160 $table = GeneralUtility::makeInstance(
1162 $table->getQuotedName(
$connection->getDatabasePlatform()),
1163 $table->getColumns(),
1165 $table->getForeignKeys(),
1167 array_merge($defaultTableOptions, $table->getOptions())
1184 if (strpos($this->connection->getServerVersion(),
'MySQL') !== 0) {
1185 foreach ($tableNames as $tableName) {
1186 $tableOptions[$tableName] = [];
1189 return $tableOptions;
1192 $queryBuilder = $this->connection->createQueryBuilder();
1193 $result = $queryBuilder
1195 'tables.TABLE_NAME AS table',
1196 'tables.ENGINE AS engine',
1197 'tables.ROW_FORMAT AS row_format',
1198 'tables.TABLE_COLLATION AS collate',
1199 'tables.TABLE_COMMENT AS comment',
1200 'CCSA.character_set_name AS charset'
1202 ->from(
'information_schema.TABLES',
'tables')
1205 'information_schema.COLLATION_CHARACTER_SET_APPLICABILITY',
1207 $queryBuilder->expr()->eq(
1208 'CCSA.collation_name',
1209 $queryBuilder->quoteIdentifier(
'tables.table_collation')
1213 $queryBuilder->expr()->eq(
1215 $queryBuilder->createNamedParameter(
'BASE TABLE', \PDO::PARAM_STR)
1217 $queryBuilder->expr()->eq(
1219 $queryBuilder->createNamedParameter($this->connection->getDatabase(), \PDO::PARAM_STR)
1224 while ($row = $result->fetch()) {
1225 $index = $row[
'table'];
1226 unset($row[
'table']);
1227 $tableOptions[$index] = $row;
1230 return $tableOptions;
1244 $databasePlatform = $this->connection->getDatabasePlatform();
1246 return GeneralUtility::makeInstance(
1248 $databasePlatform->quoteIdentifier($table->getName()),
1249 $table->getColumns(),
1250 $table->getIndexes(),
1251 $table->getForeignKeys(),
1253 $table->getOptions()
1268 $databasePlatform = $this->connection->getDatabasePlatform();
1270 return GeneralUtility::makeInstance(
1272 $databasePlatform->quoteIdentifier($column->getName()),
1274 array_diff_key($column->toArray(), [
'name',
'type'])
1289 $databasePlatform = $this->connection->getDatabasePlatform();
1291 return GeneralUtility::makeInstance(
1293 $databasePlatform->quoteIdentifier($index->getName()),
1294 $index->getColumns(),
1296 $index->isPrimary(),
1298 $index->getOptions()
1313 $databasePlatform = $this->connection->getDatabasePlatform();
1315 return GeneralUtility::makeInstance(
1316 ForeignKeyConstraint::class,
1317 $index->getLocalColumns(),
1318 $databasePlatform->quoteIdentifier($index->getForeignTableName()),
1319 $index->getForeignColumns(),
1320 $databasePlatform->quoteIdentifier($index->getName()),
1321 $index->getOptions()
1327 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
1328 return $connection->getDatabasePlatform() instanceof SqlitePlatform;