From d9b52a59deec5d58838158777e914fc9ee537295 Mon Sep 17 00:00:00 2001 From: Morton Jonuschat <m.jonuschat@mojocode.de> Date: Sat, 1 Oct 2016 17:02:31 -0700 Subject: [PATCH] [BUGFIX] Doctrine: Consider MySQL index subpart information in upgrade wizards If an index is defined on a table that is stored on a MySQL database and uses the MySQL specific subpart length feature add the information to the schema diff so that the upgrade wizards don't show false positive changes. Change-Id: I49eb73c18f7b86aad70d11f3e222c44bd1bd827f Resolves: #78024 Resolves: #79065 Releases: master Reviewed-on: https://review.typo3.org/50081 Reviewed-by: Markus Klein <markus.klein@typo3.org> Tested-by: TYPO3com <no-reply@typo3.com> Tested-by: Markus Klein <markus.klein@typo3.org> Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> Tested-by: Georg Ringer <georg.ringer@gmail.com> --- .../core/Classes/Database/ConnectionPool.php | 7 + .../SchemaIndexDefinitionListener.php | 121 ++++++++++++++++++ .../Action/Tool/ImportantActions.php | 14 +- .../Updates/FinalDatabaseSchemaUpdate.php | 6 +- 4 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php diff --git a/typo3/sysext/core/Classes/Database/ConnectionPool.php b/typo3/sysext/core/Classes/Database/ConnectionPool.php index 85700ac1082d..50be13d7f24c 100644 --- a/typo3/sysext/core/Classes/Database/ConnectionPool.php +++ b/typo3/sysext/core/Classes/Database/ConnectionPool.php @@ -22,6 +22,7 @@ use Doctrine\DBAL\Types\Type; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Schema\EventListener\SchemaAlterTableListener; use TYPO3\CMS\Core\Database\Schema\EventListener\SchemaColumnDefinitionListener; +use TYPO3\CMS\Core\Database\Schema\EventListener\SchemaIndexDefinitionListener; use TYPO3\CMS\Core\Database\Schema\Types\EnumType; use TYPO3\CMS\Core\Database\Schema\Types\SetType; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -176,6 +177,12 @@ class ConnectionPool GeneralUtility::makeInstance(SchemaColumnDefinitionListener::class) ); + // Handler for enhanced index definitions in the SchemaManager + $conn->getDatabasePlatform()->getEventManager()->addEventListener( + Events::onSchemaIndexDefinition, + GeneralUtility::makeInstance(SchemaIndexDefinitionListener::class) + ); + // Handler for adding custom database platform options to ALTER TABLE // requests in the SchemaManager $conn->getDatabasePlatform()->getEventManager()->addEventListener( diff --git a/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php b/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php new file mode 100644 index 000000000000..94fedc9036c2 --- /dev/null +++ b/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php @@ -0,0 +1,121 @@ +<?php +declare(strict_types=1); + +namespace TYPO3\CMS\Core\Database\Schema\EventListener; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; +use Doctrine\DBAL\Schema\Index; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Event listener to handle additional processing for index definitions to integrate + * MySQL index sub parts. + */ +class SchemaIndexDefinitionListener +{ + /** + * Listener for index definition events. This intercepts definitions + * for indexes and builds the appropriate Index Object taking the sub + * part length into account when a MySQL platform has been detected. + * + * @param \Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs $event + * @throws \Doctrine\DBAL\DBALException + * @throws \InvalidArgumentException + */ + public function onSchemaIndexDefinition(SchemaIndexDefinitionEventArgs $event) + { + if (strpos($event->getConnection()->getServerVersion(), 'MySQL') !== 0) { + return; + } + + $connection = $event->getConnection(); + $indexName = $event->getTableIndex()['name']; + $sql = $event->getDatabasePlatform()->getListTableIndexesSQL( + $event->getTable(), + $event->getConnection()->getDatabase() + ); + $sql .= ' AND ' . $connection->quoteIdentifier('INDEX_NAME') . ' = ' . $connection->quote($indexName); + $tableIndexes = $event->getConnection()->fetchAll($sql); + + $subPartColumns = array_filter( + $tableIndexes, + function ($column) { + return $column['Sub_Part']; + } + ); + + if (!empty($subPartColumns)) { + $event->setIndex($this->buildIndex($tableIndexes)); + $event->preventDefault(); + } + } + + /** + * Build a Doctrine Index Object based on the information + * gathered from the MySQL information schema. + * + * @param array $tableIndexRows + * @return \Doctrine\DBAL\Schema\Index + * @throws \InvalidArgumentException + */ + protected function buildIndex(array $tableIndexRows): Index + { + $data = null; + foreach ($tableIndexRows as $tableIndex) { + $tableIndex = array_change_key_case($tableIndex, CASE_LOWER); + + $tableIndex['primary'] = $tableIndex['key_name'] === 'PRIMARY'; + + if (strpos($tableIndex['index_type'], 'FULLTEXT') !== false) { + $tableIndex['flags'] = ['FULLTEXT']; + } elseif (strpos($tableIndex['index_type'], 'SPATIAL') !== false) { + $tableIndex['flags'] = ['SPATIAL']; + } + + $indexName = $tableIndex['key_name']; + $columnName = $tableIndex['column_name']; + + if ($tableIndex['sub_part'] !== null) { + $columnName .= '(' . $tableIndex['sub_part'] . ')'; + } + + if ($data === null) { + $data = [ + 'name' => $indexName, + 'columns' => [$columnName], + 'unique' => !$tableIndex['non_unique'], + 'primary' => $tableIndex['primary'], + 'flags' => $tableIndex['flags'] ?? [], + 'options' => isset($tableIndex['where']) ? ['where' => $tableIndex['where']] : [], + ]; + } else { + $data['columns'][] = $columnName; + } + } + + $index = GeneralUtility::makeInstance( + Index::class, + $data['name'], + $data['columns'], + $data['unique'], + $data['primary'], + $data['flags'], + $data['options'] + ); + + return $index; + } +} diff --git a/typo3/sysext/install/Classes/Controller/Action/Tool/ImportantActions.php b/typo3/sysext/install/Classes/Controller/Action/Tool/ImportantActions.php index d990bd4204c9..6b3ba02ba2fb 100644 --- a/typo3/sysext/install/Classes/Controller/Action/Tool/ImportantActions.php +++ b/typo3/sysext/install/Classes/Controller/Action/Tool/ImportantActions.php @@ -381,7 +381,7 @@ class ImportantActions extends Action\AbstractAction // Aggregate the per-connection statements into one flat array $addCreateChange = array_merge_recursive(...array_values($addCreateChange)); - if (isset($addCreateChange['create_table'])) { + if (!empty($addCreateChange['create_table'])) { $databaseAnalyzerSuggestion['addTable'] = []; foreach ($addCreateChange['create_table'] as $hash => $statement) { $databaseAnalyzerSuggestion['addTable'][$hash] = [ @@ -390,7 +390,7 @@ class ImportantActions extends Action\AbstractAction ]; } } - if (isset($addCreateChange['add'])) { + if (!empty($addCreateChange['add'])) { $databaseAnalyzerSuggestion['addField'] = []; foreach ($addCreateChange['add'] as $hash => $statement) { $databaseAnalyzerSuggestion['addField'][$hash] = [ @@ -399,7 +399,7 @@ class ImportantActions extends Action\AbstractAction ]; } } - if (isset($addCreateChange['change'])) { + if (!empty($addCreateChange['change'])) { $databaseAnalyzerSuggestion['change'] = []; foreach ($addCreateChange['change'] as $hash => $statement) { $databaseAnalyzerSuggestion['change'][$hash] = [ @@ -416,7 +416,7 @@ class ImportantActions extends Action\AbstractAction $dropRename = $schemaMigrationService->getUpdateSuggestions($sqlStatements, true); // Aggregate the per-connection statements into one flat array $dropRename = array_merge_recursive(...array_values($dropRename)); - if (isset($dropRename['change_table'])) { + if (!empty($dropRename['change_table'])) { $databaseAnalyzerSuggestion['renameTableToUnused'] = []; foreach ($dropRename['change_table'] as $hash => $statement) { $databaseAnalyzerSuggestion['renameTableToUnused'][$hash] = [ @@ -428,7 +428,7 @@ class ImportantActions extends Action\AbstractAction } } } - if (isset($dropRename['change'])) { + if (!empty($dropRename['change'])) { $databaseAnalyzerSuggestion['renameTableFieldToUnused'] = []; foreach ($dropRename['change'] as $hash => $statement) { $databaseAnalyzerSuggestion['renameTableFieldToUnused'][$hash] = [ @@ -437,7 +437,7 @@ class ImportantActions extends Action\AbstractAction ]; } } - if (isset($dropRename['drop'])) { + if (!empty($dropRename['drop'])) { $databaseAnalyzerSuggestion['deleteField'] = []; foreach ($dropRename['drop'] as $hash => $statement) { $databaseAnalyzerSuggestion['deleteField'][$hash] = [ @@ -446,7 +446,7 @@ class ImportantActions extends Action\AbstractAction ]; } } - if (isset($dropRename['drop_table'])) { + if (!empty($dropRename['drop_table'])) { $databaseAnalyzerSuggestion['deleteTable'] = []; foreach ($dropRename['drop_table'] as $hash => $statement) { $databaseAnalyzerSuggestion['deleteTable'][$hash] = [ diff --git a/typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php b/typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php index 701857717fdc..cff63a79955f 100644 --- a/typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php +++ b/typo3/sysext/install/Classes/Updates/FinalDatabaseSchemaUpdate.php @@ -62,7 +62,11 @@ class FinalDatabaseSchemaUpdate extends AbstractDatabaseSchemaUpdate foreach ($databaseDifferences as $schemaDiff) { // A change for a table is required if (count($schemaDiff->changedTables) !== 0) { - return true; + foreach ($schemaDiff->changedTables as $changedTable) { + if (!empty($changedTable->addedColumns) || !empty($changedTable->changedColumns)) { + return true; + } + } } } -- GitLab