diff options
-rw-r--r-- | core/Command/Db/ConvertMysqlToMB4.php | 91 | ||||
-rw-r--r-- | core/register_command.php | 1 | ||||
-rw-r--r-- | lib/private/DB/Connection.php | 8 | ||||
-rw-r--r-- | lib/private/DB/ConnectionFactory.php | 15 | ||||
-rw-r--r-- | lib/private/DB/MDB2SchemaManager.php | 10 | ||||
-rw-r--r-- | lib/private/DB/MDB2SchemaReader.php | 10 | ||||
-rw-r--r-- | lib/private/Migration/ConsoleOutput.php | 93 | ||||
-rw-r--r-- | lib/private/Repair/Collation.php | 38 | ||||
-rw-r--r-- | tests/lib/DB/MDB2SchemaReaderTest.php | 2 | ||||
-rw-r--r-- | tests/lib/DB/SchemaDiffTest.php | 4 |
10 files changed, 252 insertions, 20 deletions
diff --git a/core/Command/Db/ConvertMysqlToMB4.php b/core/Command/Db/ConvertMysqlToMB4.php new file mode 100644 index 00000000000..d456db13b11 --- /dev/null +++ b/core/Command/Db/ConvertMysqlToMB4.php @@ -0,0 +1,91 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Core\Command\Db; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use OC\Migration\ConsoleOutput; +use OC\Repair\Collation; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IURLGenerator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ConvertMysqlToMB4 extends Command { + /** @var IConfig */ + private $config; + + /** @var IDBConnection */ + private $connection; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var ILogger */ + private $logger; + + /** + * @param IConfig $config + * @param IDBConnection $connection + * @param IURLGenerator $urlGenerator + * @param ILogger $logger + */ + public function __construct(IConfig $config, IDBConnection $connection, IURLGenerator $urlGenerator, ILogger $logger) { + $this->config = $config; + $this->connection = $connection; + $this->urlGenerator = $urlGenerator; + $this->logger = $logger; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('db:convert-mysql-charset') + ->setDescription('Convert charset of MySQL/MariaDB to use utf8mb4'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if (!$this->connection->getDatabasePlatform() instanceof MySqlPlatform) { + $output->writeln("This command is only valid for MySQL/MariaDB databases."); + return 1; + } + + if (!$this->connection->supports4ByteText()) { + $url = $this->urlGenerator->linkToDocs('admin-db-conversion'); + $output->writeln("The database is not properly setup to use the charset utf8mb4."); + $output->writeln("Also check that the setting 'mysql.utf8mb4' is set to true in the config.php."); + $output->writeln("For more information please read the documentation at $url"); + return 1; + } + + // enable charset + $this->config->setSystemValue('mysql.utf8mb4', true); + + // run conversion + $coll = new Collation($this->config, $this->logger, $this->connection, false); + $coll->run(new ConsoleOutput($output)); + + return 0; + } +} diff --git a/core/register_command.php b/core/register_command.php index 6a8ab2bebe2..629fd183b06 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -84,6 +84,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) { $application->add(new OC\Core\Command\Db\GenerateChangeScript()); $application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig()))); + $application->add(new OC\Core\Command\Db\ConvertMysqlToMB4(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection(), \OC::$server->getURLGenerator(), \OC::$server->getLogger())); $application->add(new OC\Core\Command\Encryption\Disable(\OC::$server->getConfig())); $application->add(new OC\Core\Command\Encryption\Enable(\OC::$server->getConfig(), \OC::$server->getEncryptionManager())); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 4b1c560c5ca..0ee58a54ece 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -416,6 +416,12 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { * @since 11.0.0 */ public function supports4ByteText() { - return ! ($this->getDatabasePlatform() instanceof MySqlPlatform && $this->getParams()['charset'] !== 'utf8mb4'); + if (!$this->getDatabasePlatform() instanceof MySqlPlatform) { + return true; + } + if ($this->getParams()['charset'] === 'utf8mb4') { + return true; + } + return false; } } diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index d8f1fb2480d..39f15ff4a63 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -201,6 +201,21 @@ class ConnectionFactory { $connectionParams['driverOptions'] = $driverOptions; } + // set default table creation options + $connectionParams['defaultTableOptions'] = [ + 'collate' => 'utf8_bin', + 'tablePrefix' => $connectionParams['tablePrefix'] + ]; + + if($this->config->getValue('mysql.utf8mb4', false)) { + $connectionParams['defaultTableOptions'] = [ + 'collate' => 'utf8mb4_bin', + 'charset' => 'utf8mb4', + 'row_format' => 'compressed', + 'tablePrefix' => $connectionParams['tablePrefix'] + ]; + } + return $connectionParams; } } diff --git a/lib/private/DB/MDB2SchemaManager.php b/lib/private/DB/MDB2SchemaManager.php index f209991eb84..89b0d153212 100644 --- a/lib/private/DB/MDB2SchemaManager.php +++ b/lib/private/DB/MDB2SchemaManager.php @@ -33,6 +33,7 @@ use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Schema\Schema; use OCP\IDBConnection; class MDB2SchemaManager { @@ -66,7 +67,8 @@ class MDB2SchemaManager { */ public function createDbFromStructure($file) { $schemaReader = new MDB2SchemaReader(\OC::$server->getConfig(), $this->conn->getDatabasePlatform()); - $toSchema = $schemaReader->loadSchemaFromFile($file); + $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); + $toSchema = $schemaReader->loadSchemaFromFile($file, $toSchema); return $this->executeSchemaChange($toSchema); } @@ -100,7 +102,8 @@ class MDB2SchemaManager { private function readSchemaFromFile($file) { $platform = $this->conn->getDatabasePlatform(); $schemaReader = new MDB2SchemaReader(\OC::$server->getConfig(), $platform); - return $schemaReader->loadSchemaFromFile($file); + $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); + return $schemaReader->loadSchemaFromFile($file, $toSchema); } /** @@ -137,7 +140,8 @@ class MDB2SchemaManager { */ public function removeDBStructure($file) { $schemaReader = new MDB2SchemaReader(\OC::$server->getConfig(), $this->conn->getDatabasePlatform()); - $fromSchema = $schemaReader->loadSchemaFromFile($file); + $toSchema = new Schema([], [], $this->conn->getSchemaManager()->createSchemaConfig()); + $fromSchema = $schemaReader->loadSchemaFromFile($file, $toSchema); $toSchema = clone $fromSchema; /** @var $table \Doctrine\DBAL\Schema\Table */ foreach ($toSchema->getTables() as $table) { diff --git a/lib/private/DB/MDB2SchemaReader.php b/lib/private/DB/MDB2SchemaReader.php index 0a51f1b48f2..dc067b43c4a 100644 --- a/lib/private/DB/MDB2SchemaReader.php +++ b/lib/private/DB/MDB2SchemaReader.php @@ -68,21 +68,15 @@ class MDB2SchemaReader { $this->config = $config; $this->DBNAME = $config->getSystemValue('dbname', 'owncloud'); $this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_'); - - // Oracle does not support longer index names then 30 characters. - // We use this limit for all DBs to make sure it does not cause a - // problem. - $this->schemaConfig = new SchemaConfig(); - $this->schemaConfig->setMaxIdentifierLength(30); } /** * @param string $file + * @param Schema $schema * @return Schema * @throws \DomainException */ - public function loadSchemaFromFile($file) { - $schema = new \Doctrine\DBAL\Schema\Schema(); + public function loadSchemaFromFile($file, Schema $schema) { $loadEntities = libxml_disable_entity_loader(false); $xml = simplexml_load_file($file); libxml_disable_entity_loader($loadEntities); diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php new file mode 100644 index 00000000000..892a2f43419 --- /dev/null +++ b/lib/private/Migration/ConsoleOutput.php @@ -0,0 +1,93 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Migration; + + +use OCP\Migration\IOutput; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class SimpleOutput + * + * Just a simple IOutput implementation with writes messages to the log file. + * Alternative implementations will write to the console or to the web ui (web update case) + * + * @package OC\Migration + */ +class ConsoleOutput implements IOutput { + + /** @var OutputInterface */ + private $output; + + /** @var ProgressBar */ + private $progressBar; + + public function __construct(OutputInterface $output) { + $this->output = $output; + } + + /** + * @param string $message + */ + public function info($message) { + $this->output->writeln("<info>$message</info>"); + } + + /** + * @param string $message + */ + public function warning($message) { + $this->output->writeln("<comment>$message</comment>"); + } + + /** + * @param int $max + */ + public function startProgress($max = 0) { + if (!is_null($this->progressBar)) { + $this->progressBar->finish(); + } + $this->progressBar = new ProgressBar($this->output); + $this->progressBar->start($max); + } + + /** + * @param int $step + * @param string $description + */ + public function advance($step = 1, $description = '') { + if (!is_null($this->progressBar)) { + $this->progressBar = new ProgressBar($this->output); + $this->progressBar->start(); + } + $this->progressBar->advance($step); + } + + public function finishProgress() { + if (is_null($this->progressBar)) { + return; + } + $this->progressBar->finish(); + } +} diff --git a/lib/private/Repair/Collation.php b/lib/private/Repair/Collation.php index 12e83c2b981..a01700e047e 100644 --- a/lib/private/Repair/Collation.php +++ b/lib/private/Repair/Collation.php @@ -88,6 +88,11 @@ class Collation implements IRepairStep { } $output->info("Change collation for $table ..."); + if ($characterSet === 'utf8mb4') { + // need to set row compression first + $query = $this->connection->prepare('ALTER TABLE `' . $table . '` ROW_FORMAT=COMPRESSED;'); + $query->execute(); + } $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET ' . $characterSet . ' COLLATE ' . $characterSet . '_bin;'); try { $query->execute(); @@ -99,16 +104,21 @@ class Collation implements IRepairStep { } } } + if (empty($tables)) { + $output->info('All tables already have the correct collation -> nothing to do'); + } } /** - * @param \Doctrine\DBAL\Connection $connection + * @param IDBConnection $connection * @return string[] */ - protected function getAllNonUTF8BinTables($connection) { + protected function getAllNonUTF8BinTables(IDBConnection $connection) { $dbName = $this->config->getSystemValue("dbname"); $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; - $rows = $connection->fetchAll( + + // fetch tables by columns + $statement = $connection->executeQuery( "SELECT DISTINCT(TABLE_NAME) AS `table`" . " FROM INFORMATION_SCHEMA . COLUMNS" . " WHERE TABLE_SCHEMA = ?" . @@ -116,11 +126,27 @@ class Collation implements IRepairStep { " AND TABLE_NAME LIKE \"*PREFIX*%\"", array($dbName) ); - $result = array(); + $rows = $statement->fetchAll(); + $result = []; foreach ($rows as $row) { - $result[] = $row['table']; + $result[$row['table']] = true; } - return $result; + + // fetch tables by collation + $statement = $connection->executeQuery( + "SELECT DISTINCT(TABLE_NAME) AS `table`" . + " FROM INFORMATION_SCHEMA . TABLES" . + " WHERE TABLE_SCHEMA = ?" . + " AND TABLE_COLLATION <> '" . $characterSet . "_bin'" . + " AND TABLE_NAME LIKE \"*PREFIX*%\"", + [$dbName] + ); + $rows = $statement->fetchAll(); + foreach ($rows as $row) { + $result[$row['table']] = true; + } + + return array_keys($result); } } diff --git a/tests/lib/DB/MDB2SchemaReaderTest.php b/tests/lib/DB/MDB2SchemaReaderTest.php index dcec6ae593e..3daf0dd7589 100644 --- a/tests/lib/DB/MDB2SchemaReaderTest.php +++ b/tests/lib/DB/MDB2SchemaReaderTest.php @@ -47,7 +47,7 @@ class MDB2SchemaReaderTest extends TestCase { public function testRead() { $reader = new MDB2SchemaReader($this->getConfig(), new MySqlPlatform()); - $schema = $reader->loadSchemaFromFile(__DIR__ . '/testschema.xml'); + $schema = $reader->loadSchemaFromFile(__DIR__ . '/testschema.xml', new Schema()); $this->assertCount(1, $schema->getTables()); $table = $schema->getTable('test_table'); diff --git a/tests/lib/DB/SchemaDiffTest.php b/tests/lib/DB/SchemaDiffTest.php index 88c9abeb431..f74d800bfec 100644 --- a/tests/lib/DB/SchemaDiffTest.php +++ b/tests/lib/DB/SchemaDiffTest.php @@ -21,6 +21,7 @@ namespace Test\DB; +use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaDiff; use OC\DB\MDB2SchemaManager; use OC\DB\MDB2SchemaReader; @@ -79,7 +80,8 @@ class SchemaDiffTest extends TestCase { $this->manager->createDbFromStructure($schemaFile); $schemaReader = new MDB2SchemaReader($this->config, $this->connection->getDatabasePlatform()); - $endSchema = $schemaReader->loadSchemaFromFile($schemaFile); + $toSchema = new Schema([], [], $this->connection->getSchemaManager()->createSchemaConfig()); + $endSchema = $schemaReader->loadSchemaFromFile($schemaFile, $toSchema); // get the diff /** @var SchemaDiff $diff */ |