Collation.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\Repair;
  8. use Doctrine\DBAL\Exception\DriverException;
  9. use OCP\IConfig;
  10. use OCP\IDBConnection;
  11. use OCP\Migration\IOutput;
  12. use OCP\Migration\IRepairStep;
  13. use Psr\Log\LoggerInterface;
  14. class Collation implements IRepairStep {
  15. /** @var IConfig */
  16. protected $config;
  17. protected LoggerInterface $logger;
  18. /** @var IDBConnection */
  19. protected $connection;
  20. /** @var bool */
  21. protected $ignoreFailures;
  22. /**
  23. * @param bool $ignoreFailures
  24. */
  25. public function __construct(
  26. IConfig $config,
  27. LoggerInterface $logger,
  28. IDBConnection $connection,
  29. $ignoreFailures
  30. ) {
  31. $this->connection = $connection;
  32. $this->config = $config;
  33. $this->logger = $logger;
  34. $this->ignoreFailures = $ignoreFailures;
  35. }
  36. public function getName() {
  37. return 'Repair MySQL collation';
  38. }
  39. /**
  40. * Fix mime types
  41. */
  42. public function run(IOutput $output) {
  43. if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) {
  44. $output->info('Not a mysql database -> nothing to do');
  45. return;
  46. }
  47. $characterSet = $this->config->getSystemValueBool('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
  48. $tables = $this->getAllNonUTF8BinTables($this->connection);
  49. foreach ($tables as $table) {
  50. $output->info("Change row format for $table ...");
  51. $query = $this->connection->prepare('ALTER TABLE `' . $table . '` ROW_FORMAT = DYNAMIC;');
  52. try {
  53. $query->execute();
  54. } catch (DriverException $e) {
  55. // Just log this
  56. $this->logger->error($e->getMessage(), ['exception' => $e]);
  57. if (!$this->ignoreFailures) {
  58. throw $e;
  59. }
  60. }
  61. $output->info("Change collation for $table ...");
  62. $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET ' . $characterSet . ' COLLATE ' . $characterSet . '_bin;');
  63. try {
  64. $query->execute();
  65. } catch (DriverException $e) {
  66. // Just log this
  67. $this->logger->error($e->getMessage(), ['exception' => $e]);
  68. if (!$this->ignoreFailures) {
  69. throw $e;
  70. }
  71. }
  72. }
  73. if (empty($tables)) {
  74. $output->info('All tables already have the correct collation -> nothing to do');
  75. }
  76. }
  77. /**
  78. * @param IDBConnection $connection
  79. * @return string[]
  80. */
  81. protected function getAllNonUTF8BinTables(IDBConnection $connection) {
  82. $dbName = $this->config->getSystemValueString("dbname");
  83. $characterSet = $this->config->getSystemValueBool('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
  84. // fetch tables by columns
  85. $statement = $connection->executeQuery(
  86. "SELECT DISTINCT(TABLE_NAME) AS `table`" .
  87. " FROM INFORMATION_SCHEMA . COLUMNS" .
  88. " WHERE TABLE_SCHEMA = ?" .
  89. " AND (COLLATION_NAME <> '" . $characterSet . "_bin' OR CHARACTER_SET_NAME <> '" . $characterSet . "')" .
  90. " AND TABLE_NAME LIKE '*PREFIX*%'",
  91. [$dbName]
  92. );
  93. $rows = $statement->fetchAll();
  94. $result = [];
  95. foreach ($rows as $row) {
  96. $result[$row['table']] = true;
  97. }
  98. // fetch tables by collation
  99. $statement = $connection->executeQuery(
  100. "SELECT DISTINCT(TABLE_NAME) AS `table`" .
  101. " FROM INFORMATION_SCHEMA . TABLES" .
  102. " WHERE TABLE_SCHEMA = ?" .
  103. " AND TABLE_COLLATION <> '" . $characterSet . "_bin'" .
  104. " AND TABLE_NAME LIKE '*PREFIX*%'",
  105. [$dbName]
  106. );
  107. $rows = $statement->fetchAll();
  108. foreach ($rows as $row) {
  109. $result[$row['table']] = true;
  110. }
  111. return array_keys($result);
  112. }
  113. }