aboutsummaryrefslogtreecommitdiffstats
path: root/core/Command/Db
diff options
context:
space:
mode:
Diffstat (limited to 'core/Command/Db')
-rw-r--r--core/Command/Db/AddMissingColumns.php24
-rw-r--r--core/Command/Db/AddMissingIndices.php46
-rw-r--r--core/Command/Db/AddMissingPrimaryKeys.php24
-rw-r--r--core/Command/Db/ConvertFilecacheBigInt.php37
-rw-r--r--core/Command/Db/ConvertMysqlToMB4.php31
-rw-r--r--core/Command/Db/ConvertType.php83
-rw-r--r--core/Command/Db/ExpectedSchema.php68
-rw-r--r--core/Command/Db/ExportSchema.php44
-rw-r--r--core/Command/Db/Migrations/ExecuteCommand.php24
-rw-r--r--core/Command/Db/Migrations/GenerateCommand.php71
-rw-r--r--core/Command/Db/Migrations/GenerateMetadataCommand.php79
-rw-r--r--core/Command/Db/Migrations/MigrateCommand.php23
-rw-r--r--core/Command/Db/Migrations/PreviewCommand.php111
-rw-r--r--core/Command/Db/Migrations/StatusCommand.php24
-rw-r--r--core/Command/Db/SchemaEncoder.php115
15 files changed, 517 insertions, 287 deletions
diff --git a/core/Command/Db/AddMissingColumns.php b/core/Command/Db/AddMissingColumns.php
index 07763c66154..33b4b24a6cb 100644
--- a/core/Command/Db/AddMissingColumns.php
+++ b/core/Command/Db/AddMissingColumns.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
@@ -55,7 +37,7 @@ class AddMissingColumns extends Command {
$this
->setName('db:add-missing-columns')
->setDescription('Add missing optional columns to the database tables')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, "Output the SQL queries instead of running them.");
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the SQL queries instead of running them.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/core/Command/Db/AddMissingIndices.php b/core/Command/Db/AddMissingIndices.php
index 1e10b6152ce..eec0aedce11 100644
--- a/core/Command/Db/AddMissingIndices.php
+++ b/core/Command/Db/AddMissingIndices.php
@@ -3,33 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Mario Danic <mario@lovelyhq.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Citharel <nextcloud@tcit.fr>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
@@ -62,7 +37,7 @@ class AddMissingIndices extends Command {
$this
->setName('db:add-missing-indices')
->setDescription('Add missing indices to the database tables')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, "Output the SQL queries instead of running them.");
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the SQL queries instead of running them.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -111,14 +86,7 @@ class AddMissingIndices extends Command {
if ($schema->hasTable($toReplaceIndex['tableName'])) {
$table = $schema->getTable($toReplaceIndex['tableName']);
- $allOldIndicesExists = true;
- foreach ($toReplaceIndex['oldIndexNames'] as $oldIndexName) {
- if (!$table->hasIndex($oldIndexName)) {
- $allOldIndicesExists = false;
- }
- }
-
- if (!$allOldIndicesExists) {
+ if ($table->hasIndex($toReplaceIndex['newIndexName'])) {
continue;
}
@@ -135,8 +103,10 @@ class AddMissingIndices extends Command {
}
foreach ($toReplaceIndex['oldIndexNames'] as $oldIndexName) {
- $output->writeln('<info>Removing ' . $oldIndexName . ' index from the ' . $table->getName() . ' table</info>');
- $table->dropIndex($oldIndexName);
+ if ($table->hasIndex($oldIndexName)) {
+ $output->writeln('<info>Removing ' . $oldIndexName . ' index from the ' . $table->getName() . ' table</info>');
+ $table->dropIndex($oldIndexName);
+ }
}
if (!$dryRun) {
diff --git a/core/Command/Db/AddMissingPrimaryKeys.php b/core/Command/Db/AddMissingPrimaryKeys.php
index 658eb0b0f5a..1eb11c894fa 100644
--- a/core/Command/Db/AddMissingPrimaryKeys.php
+++ b/core/Command/Db/AddMissingPrimaryKeys.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
@@ -55,7 +37,7 @@ class AddMissingPrimaryKeys extends Command {
$this
->setName('db:add-missing-primary-keys')
->setDescription('Add missing primary keys to the database tables')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, "Output the SQL queries instead of running them.");
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the SQL queries instead of running them.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/core/Command/Db/ConvertFilecacheBigInt.php b/core/Command/Db/ConvertFilecacheBigInt.php
index 44cd81cd7eb..0d96d139701 100644
--- a/core/Command/Db/ConvertFilecacheBigInt.php
+++ b/core/Command/Db/ConvertFilecacheBigInt.php
@@ -1,42 +1,18 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- *
- * @author Alecks Gates <alecks.g@gmail.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Georg Ehrke <oc.list@georgehrke.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author timm2k <timm2k@gmx.de>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
-use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Types\Type;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
use OCP\DB\Types;
+use OCP\IDBConnection;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
@@ -79,7 +55,7 @@ class ConvertFilecacheBigInt extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$schema = new SchemaWrapper($this->connection);
- $isSqlite = $this->connection->getDatabasePlatform() instanceof SqlitePlatform;
+ $isSqlite = $this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE;
$updates = [];
$tables = static::getColumnsByTable();
@@ -115,6 +91,7 @@ class ConvertFilecacheBigInt extends Command {
$output->writeln('<comment>This can take up to hours, depending on the number of files in your instance!</comment>');
if ($input->isInteractive()) {
+ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Continue with the conversion (y/n)? [n] ', false);
diff --git a/core/Command/Db/ConvertMysqlToMB4.php b/core/Command/Db/ConvertMysqlToMB4.php
index a66fb46fc51..926e56c4300 100644
--- a/core/Command/Db/ConvertMysqlToMB4.php
+++ b/core/Command/Db/ConvertMysqlToMB4.php
@@ -1,30 +1,11 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db;
-use Doctrine\DBAL\Platforms\MySQLPlatform;
use OC\DB\MySqlTools;
use OC\Migration\ConsoleOutput;
use OC\Repair\Collation;
@@ -53,15 +34,15 @@ class ConvertMysqlToMB4 extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- if (!$this->connection->getDatabasePlatform() instanceof MySQLPlatform) {
- $output->writeln("This command is only valid for MySQL/MariaDB databases.");
+ if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) {
+ $output->writeln('This command is only valid for MySQL/MariaDB databases.');
return 1;
}
$tools = new MySqlTools();
if (!$tools->supports4ByteCharset($this->connection)) {
$url = $this->urlGenerator->linkToDocs('admin-mysql-utf8mb4');
- $output->writeln("The database is not properly setup to use the charset utf8mb4.");
+ $output->writeln('The database is not properly setup to use the charset utf8mb4.');
$output->writeln("For more information please read the documentation at $url");
return 1;
}
diff --git a/core/Command/Db/ConvertType.php b/core/Command/Db/ConvertType.php
index db618e938c0..0067bec4d9e 100644
--- a/core/Command/Db/ConvertType.php
+++ b/core/Command/Db/ConvertType.php
@@ -1,34 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Bernhard Ostertag <bernieo.code@gmx.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Łukasz Buśko <busko.lukasz@pm.me>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sander Ruitenbeek <sander@grids.be>
- * @author Simon Spannagel <simonspa@kth.se>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db;
@@ -38,9 +13,12 @@ use Doctrine\DBAL\Schema\Table;
use OC\DB\Connection;
use OC\DB\ConnectionFactory;
use OC\DB\MigrationService;
+use OC\DB\PgSqlTools;
+use OCP\App\IAppManager;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
use OCP\IConfig;
+use OCP\Server;
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
use Symfony\Component\Console\Command\Command;
@@ -61,6 +39,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
public function __construct(
protected IConfig $config,
protected ConnectionFactory $connectionFactory,
+ protected IAppManager $appManager,
) {
parent::__construct();
}
@@ -167,10 +146,13 @@ class ConvertType extends Command implements CompletionAwareInterface {
if ($input->isInteractive()) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
- $question = new Question('What is the database password?');
+ $question = new Question('What is the database password (press <enter> for none)? ');
$question->setHidden(true);
$question->setHiddenFallback(false);
$password = $helper->ask($input, $output, $question);
+ if ($password === null) {
+ $password = ''; // possibly unnecessary
+ }
$input->setOption('password', $password);
return;
}
@@ -181,7 +163,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$this->readPassword($input, $output);
/** @var Connection $fromDB */
- $fromDB = \OC::$server->get(Connection::class);
+ $fromDB = Server::get(Connection::class);
$toDB = $this->getToDBConnection($input, $output);
if ($input->getOption('clear-schema')) {
@@ -228,11 +210,13 @@ class ConvertType extends Command implements CompletionAwareInterface {
$toMS->migrate($currentMigration);
}
- $apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
+ $apps = $input->getOption('all-apps')
+ ? $this->appManager->getAllAppsInAppsFolders()
+ : $this->appManager->getEnabledApps();
foreach ($apps as $app) {
- $output->writeln('<info> - '.$app.'</info>');
+ $output->writeln('<info> - ' . $app . '</info>');
// Make sure autoloading works...
- \OC_App::loadApp($app);
+ $this->appManager->loadApp($app);
$fromMS = new MigrationService($app, $fromDB);
$currentMigration = $fromMS->getMigration('current');
if ($currentMigration !== '0') {
@@ -244,16 +228,31 @@ class ConvertType extends Command implements CompletionAwareInterface {
protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
$type = $input->getArgument('type');
- $connectionParams = $this->connectionFactory->createConnectionParams();
+ $connectionParams = $this->connectionFactory->createConnectionParams(type: $type);
$connectionParams = array_merge($connectionParams, [
'host' => $input->getArgument('hostname'),
'user' => $input->getArgument('username'),
'password' => $input->getOption('password'),
'dbname' => $input->getArgument('database'),
]);
+
+ // parse port
if ($input->getOption('port')) {
$connectionParams['port'] = $input->getOption('port');
}
+
+ // parse hostname for unix socket
+ if (preg_match('/^(.+)(:(\d+|[^:]+))?$/', $input->getArgument('hostname'), $matches)) {
+ $connectionParams['host'] = $matches[1];
+ if (isset($matches[3])) {
+ if (is_numeric($matches[3])) {
+ $connectionParams['port'] = $matches[3];
+ } else {
+ $connectionParams['unix_socket'] = $matches[3];
+ }
+ }
+ }
+
return $this->connectionFactory->getConnection($type, $connectionParams);
}
@@ -263,7 +262,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$output->writeln('<info>Clearing schema in new database</info>');
}
foreach ($toTables as $table) {
- $db->getSchemaManager()->dropTable($table);
+ $db->createSchemaManager()->dropTable($table);
}
}
@@ -276,7 +275,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
}
return preg_match($filterExpression, $asset) !== false;
});
- return $db->getSchemaManager()->listTableNames();
+ return $db->createSchemaManager()->listTableNames();
}
/**
@@ -298,7 +297,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$query->automaticTablePrefix(false);
$query->select($query->func()->count('*', 'num_entries'))
->from($table->getName());
- $result = $query->execute();
+ $result = $query->executeQuery();
$count = $result->fetchOne();
$result->closeCursor();
@@ -337,7 +336,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
for ($chunk = 0; $chunk < $numChunks; $chunk++) {
$query->setFirstResult($chunk * $chunkSize);
- $result = $query->execute();
+ $result = $query->executeQuery();
try {
$toDB->beginTransaction();
@@ -404,11 +403,11 @@ class ConvertType extends Command implements CompletionAwareInterface {
try {
// copy table rows
foreach ($tables as $table) {
- $output->writeln('<info> - '.$table.'</info>');
+ $output->writeln('<info> - ' . $table . '</info>');
$this->copyTable($fromDB, $toDB, $schema->getTable($table), $input, $output);
}
if ($input->getArgument('type') === 'pgsql') {
- $tools = new \OC\DB\PgSqlTools($this->config);
+ $tools = new PgSqlTools($this->config);
$tools->resynchronizeDatabaseSequences($toDB);
}
// save new database config
@@ -427,7 +426,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$dbName = $input->getArgument('database');
$password = $input->getOption('password');
if ($input->getOption('port')) {
- $dbHost .= ':'.$input->getOption('port');
+ $dbHost .= ':' . $input->getOption('port');
}
$this->config->setSystemValues([
diff --git a/core/Command/Db/ExpectedSchema.php b/core/Command/Db/ExpectedSchema.php
new file mode 100644
index 00000000000..1f35daba089
--- /dev/null
+++ b/core/Command/Db/ExpectedSchema.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Db;
+
+use Doctrine\DBAL\Schema\Schema;
+use OC\Core\Command\Base;
+use OC\DB\Connection;
+use OC\DB\MigrationService;
+use OC\DB\SchemaWrapper;
+use OC\Migration\NullOutput;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExpectedSchema extends Base {
+ public function __construct(
+ protected Connection $connection,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('db:schema:expected')
+ ->setDescription('Export the expected database schema for a fresh installation')
+ ->setHelp("Note that the expected schema might not exactly match the exported live schema as the expected schema doesn't take into account any database wide settings or defaults.")
+ ->addOption('sql', null, InputOption::VALUE_NONE, 'Dump the SQL statements for creating the expected schema');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $schema = new Schema();
+
+ $this->applyMigrations('core', $schema);
+
+ $apps = \OC_App::getEnabledApps();
+ foreach ($apps as $app) {
+ $this->applyMigrations($app, $schema);
+ }
+
+ $sql = $input->getOption('sql');
+ if ($sql) {
+ $output->writeln($schema->toSql($this->connection->getDatabasePlatform()));
+ } else {
+ $encoder = new SchemaEncoder();
+ $this->writeArrayInOutputFormat($input, $output, $encoder->encodeSchema($schema, $this->connection->getDatabasePlatform()));
+ }
+
+ return 0;
+ }
+
+ private function applyMigrations(string $app, Schema $schema): void {
+ $output = new NullOutput();
+ $ms = new MigrationService($app, $this->connection, $output);
+ foreach ($ms->getAvailableVersions() as $version) {
+ $migration = $ms->createInstance($version);
+ $migration->changeSchema($output, function () use (&$schema) {
+ return new SchemaWrapper($this->connection, $schema);
+ }, []);
+ }
+ }
+}
diff --git a/core/Command/Db/ExportSchema.php b/core/Command/Db/ExportSchema.php
new file mode 100644
index 00000000000..581824eea5f
--- /dev/null
+++ b/core/Command/Db/ExportSchema.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Db;
+
+use OC\Core\Command\Base;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExportSchema extends Base {
+ public function __construct(
+ protected IDBConnection $connection,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('db:schema:export')
+ ->setDescription('Export the current database schema')
+ ->addOption('sql', null, InputOption::VALUE_NONE, 'Dump the SQL statements for creating a copy of the schema');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $schema = $this->connection->createSchema();
+ $sql = $input->getOption('sql');
+ if ($sql) {
+ $output->writeln($schema->toSql($this->connection->getDatabasePlatform()));
+ } else {
+ $encoder = new SchemaEncoder();
+ $this->writeArrayInOutputFormat($input, $output, $encoder->encodeSchema($schema, $this->connection->getDatabasePlatform()));
+ }
+
+ return 0;
+ }
+}
diff --git a/core/Command/Db/Migrations/ExecuteCommand.php b/core/Command/Db/Migrations/ExecuteCommand.php
index c75b575ab6c..a89072c1ad1 100644
--- a/core/Command/Db/Migrations/ExecuteCommand.php
+++ b/core/Command/Db/Migrations/ExecuteCommand.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
diff --git a/core/Command/Db/Migrations/GenerateCommand.php b/core/Command/Db/Migrations/GenerateCommand.php
index 47f65b5a11b..a75280fa8b1 100644
--- a/core/Command/Db/Migrations/GenerateCommand.php
+++ b/core/Command/Db/Migrations/GenerateCommand.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
@@ -33,37 +16,21 @@ use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareI
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class GenerateCommand extends Command implements CompletionAwareInterface {
- protected static $_templateSimple =
- '<?php
+ protected static $_templateSimple
+ = '<?php
declare(strict_types=1);
/**
- * @copyright Copyright (c) {{year}} FIXME Your name <your@email.com>
- *
- * FIXME @author Your name <your@email.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: {{year}} Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace {{namespace}};
@@ -72,6 +39,7 @@ use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
+use Override;
/**
* FIXME Auto-generated migration step: Please modify to your needs!
@@ -83,6 +51,7 @@ class {{classname}} extends SimpleMigrationStep {
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
+ #[Override]
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
@@ -92,6 +61,7 @@ class {{classname}} extends SimpleMigrationStep {
* @param array $options
* @return null|ISchemaWrapper
*/
+ #[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
{{schemabody}}
}
@@ -101,18 +71,16 @@ class {{classname}} extends SimpleMigrationStep {
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
+ #[Override]
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
}
';
- protected Connection $connection;
- protected IAppManager $appManager;
-
- public function __construct(Connection $connection, IAppManager $appManager) {
- $this->connection = $connection;
- $this->appManager = $appManager;
-
+ public function __construct(
+ protected Connection $connection,
+ protected IAppManager $appManager,
+ ) {
parent::__construct();
}
@@ -147,7 +115,7 @@ class {{classname}} extends SimpleMigrationStep {
if ($fullVersion) {
[$major, $minor] = explode('.', $fullVersion);
- $shouldVersion = (string) ((int)$major * 1000 + (int)$minor);
+ $shouldVersion = (string)((int)$major * 1000 + (int)$minor);
if ($version !== $shouldVersion) {
$output->writeln('<comment>Unexpected migration version for current version: ' . $fullVersion . '</comment>');
$output->writeln('<comment> - Pattern: XYYY </comment>');
@@ -155,6 +123,7 @@ class {{classname}} extends SimpleMigrationStep {
$output->writeln('<comment> - Actual: ' . $version . '</comment>');
if ($input->isInteractive()) {
+ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Continue with your given version? (y/n) [n] ', false);
@@ -190,7 +159,7 @@ class {{classname}} extends SimpleMigrationStep {
*/
public function completeArgumentValues($argumentName, CompletionContext $context) {
if ($argumentName === 'app') {
- $allApps = \OC_App::getAllApps();
+ $allApps = $this->appManager->getAllAppsInAppsFolders();
return array_diff($allApps, \OC_App::getEnabledApps(true, true));
}
diff --git a/core/Command/Db/Migrations/GenerateMetadataCommand.php b/core/Command/Db/Migrations/GenerateMetadataCommand.php
new file mode 100644
index 00000000000..581259c99df
--- /dev/null
+++ b/core/Command/Db/Migrations/GenerateMetadataCommand.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Db\Migrations;
+
+use OC\Migration\MetadataManager;
+use OCP\App\IAppManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @since 30.0.0
+ */
+class GenerateMetadataCommand extends Command {
+ public function __construct(
+ private readonly MetadataManager $metadataManager,
+ private readonly IAppManager $appManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this->setName('migrations:generate-metadata')
+ ->setHidden(true)
+ ->setDescription('Generate metadata from DB migrations - internal and should not be used');
+
+ parent::configure();
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $output->writeln(
+ json_encode(
+ [
+ 'migrations' => $this->extractMigrationMetadata()
+ ],
+ JSON_PRETTY_PRINT
+ )
+ );
+
+ return 0;
+ }
+
+ private function extractMigrationMetadata(): array {
+ return [
+ 'core' => $this->extractMigrationMetadataFromCore(),
+ 'apps' => $this->extractMigrationMetadataFromApps()
+ ];
+ }
+
+ private function extractMigrationMetadataFromCore(): array {
+ return $this->metadataManager->extractMigrationAttributes('core');
+ }
+
+ /**
+ * get all apps and extract attributes
+ *
+ * @return array
+ * @throws \Exception
+ */
+ private function extractMigrationMetadataFromApps(): array {
+ $allApps = $this->appManager->getAllAppsInAppsFolders();
+ $metadata = [];
+ foreach ($allApps as $appId) {
+ // We need to load app before being able to extract Migrations
+ $alreadyLoaded = $this->appManager->isAppLoaded($appId);
+ if (!$alreadyLoaded) {
+ $this->appManager->loadApp($appId);
+ }
+ $metadata[$appId] = $this->metadataManager->extractMigrationAttributes($appId);
+ }
+ return $metadata;
+ }
+}
diff --git a/core/Command/Db/Migrations/MigrateCommand.php b/core/Command/Db/Migrations/MigrateCommand.php
index 3e11b32665a..2e02f031479 100644
--- a/core/Command/Db/Migrations/MigrateCommand.php
+++ b/core/Command/Db/Migrations/MigrateCommand.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
diff --git a/core/Command/Db/Migrations/PreviewCommand.php b/core/Command/Db/Migrations/PreviewCommand.php
new file mode 100644
index 00000000000..f5b850fff76
--- /dev/null
+++ b/core/Command/Db/Migrations/PreviewCommand.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Db\Migrations;
+
+use OC\Migration\MetadataManager;
+use OC\Updater\ReleaseMetadata;
+use OCP\Migration\Attributes\MigrationAttribute;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Helper\TableCell;
+use Symfony\Component\Console\Helper\TableCellStyle;
+use Symfony\Component\Console\Helper\TableSeparator;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @since 30.0.0
+ */
+class PreviewCommand extends Command {
+ private bool $initiated = false;
+ public function __construct(
+ private readonly MetadataManager $metadataManager,
+ private readonly ReleaseMetadata $releaseMetadata,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('migrations:preview')
+ ->setDescription('Get preview of available DB migrations in case of initiating an upgrade')
+ ->addArgument('version', InputArgument::REQUIRED, 'The destination version number');
+
+ parent::configure();
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $version = $input->getArgument('version');
+ if (filter_var($version, FILTER_VALIDATE_URL)) {
+ $metadata = $this->releaseMetadata->downloadMetadata($version);
+ } elseif (str_starts_with($version, '/')) {
+ $metadata = json_decode(file_get_contents($version), true, flags: JSON_THROW_ON_ERROR);
+ } else {
+ $metadata = $this->releaseMetadata->getMetadata($version);
+ }
+
+ $parsed = $this->metadataManager->getMigrationsAttributesFromReleaseMetadata($metadata['migrations'] ?? [], true);
+
+ $table = new Table($output);
+ $this->displayMigrations($table, 'core', $parsed['core'] ?? []);
+ foreach ($parsed['apps'] as $appId => $migrations) {
+ if (!empty($migrations)) {
+ $this->displayMigrations($table, $appId, $migrations);
+ }
+ }
+ $table->render();
+
+ $unsupportedApps = $this->metadataManager->getUnsupportedApps($metadata['migrations']);
+ if (!empty($unsupportedApps)) {
+ $output->writeln('');
+ $output->writeln('Those apps are not supporting metadata yet and might initiate migrations on upgrade: <info>' . implode(', ', $unsupportedApps) . '</info>');
+ }
+
+ return 0;
+ }
+
+ private function displayMigrations(Table $table, string $appId, array $data): void {
+ if (empty($data)) {
+ return;
+ }
+
+ if ($this->initiated) {
+ $table->addRow(new TableSeparator());
+ }
+ $this->initiated = true;
+
+ $table->addRow(
+ [
+ new TableCell(
+ $appId,
+ [
+ 'colspan' => 2,
+ 'style' => new TableCellStyle(['cellFormat' => '<info>%s</info>'])
+ ]
+ )
+ ]
+ )->addRow(new TableSeparator());
+
+ /** @var MigrationAttribute[] $attributes */
+ foreach ($data as $migration => $attributes) {
+ $attributesStr = [];
+ if (empty($attributes)) {
+ $attributesStr[] = '<comment>(metadata not set)</comment>';
+ }
+ foreach ($attributes as $attribute) {
+ $definition = '<info>' . $attribute->definition() . '</info>';
+ $definition .= empty($attribute->getDescription()) ? '' : "\n " . $attribute->getDescription();
+ $definition .= empty($attribute->getNotes()) ? '' : "\n <comment>" . implode("</comment>\n <comment>", $attribute->getNotes()) . '</comment>';
+ $attributesStr[] = $definition;
+ }
+ $table->addRow([$migration, implode("\n", $attributesStr)]);
+ }
+ }
+}
diff --git a/core/Command/Db/Migrations/StatusCommand.php b/core/Command/Db/Migrations/StatusCommand.php
index 52bc51a169f..97ecc76a924 100644
--- a/core/Command/Db/Migrations/StatusCommand.php
+++ b/core/Command/Db/Migrations/StatusCommand.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
diff --git a/core/Command/Db/SchemaEncoder.php b/core/Command/Db/SchemaEncoder.php
new file mode 100644
index 00000000000..beae3a81264
--- /dev/null
+++ b/core/Command/Db/SchemaEncoder.php
@@ -0,0 +1,115 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Db;
+
+use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Types\PhpIntegerMappingType;
+use Doctrine\DBAL\Types\Type;
+
+class SchemaEncoder {
+ /**
+ * Encode a DBAL schema to json, performing some normalization based on the database platform
+ *
+ * @param Schema $schema
+ * @param AbstractPlatform $platform
+ * @return array
+ */
+ public function encodeSchema(Schema $schema, AbstractPlatform $platform): array {
+ $encoded = ['tables' => [], 'sequences' => []];
+ foreach ($schema->getTables() as $table) {
+ $encoded[$table->getName()] = $this->encodeTable($table, $platform);
+ }
+ ksort($encoded);
+ return $encoded;
+ }
+
+ /**
+ * @psalm-type ColumnArrayType =
+ */
+ private function encodeTable(Table $table, AbstractPlatform $platform): array {
+ $encoded = ['columns' => [], 'indexes' => []];
+ foreach ($table->getColumns() as $column) {
+ /**
+ * @var array{
+ * name: string,
+ * default: mixed,
+ * notnull: bool,
+ * length: ?int,
+ * precision: int,
+ * scale: int,
+ * unsigned: bool,
+ * fixed: bool,
+ * autoincrement: bool,
+ * comment: string,
+ * columnDefinition: ?string,
+ * collation?: string,
+ * charset?: string,
+ * jsonb?: bool,
+ * } $data
+ **/
+ $data = $column->toArray();
+ $data['type'] = Type::getTypeRegistry()->lookupName($column->getType());
+ $data['default'] = $column->getType()->convertToPHPValue($column->getDefault(), $platform);
+ if ($platform instanceof PostgreSQLPlatform) {
+ $data['unsigned'] = false;
+ if ($column->getType() instanceof PhpIntegerMappingType) {
+ $data['length'] = null;
+ }
+ unset($data['jsonb']);
+ } elseif ($platform instanceof AbstractMySqlPlatform) {
+ if ($column->getType() instanceof PhpIntegerMappingType) {
+ $data['length'] = null;
+ } elseif (in_array($data['type'], ['text', 'blob', 'datetime', 'float', 'json'])) {
+ $data['length'] = 0;
+ }
+ unset($data['collation']);
+ unset($data['charset']);
+ }
+ if ($data['type'] === 'string' && $data['length'] === null) {
+ $data['length'] = 255;
+ }
+ $encoded['columns'][$column->getName()] = $data;
+ }
+ ksort($encoded['columns']);
+ foreach ($table->getIndexes() as $index) {
+ $options = $index->getOptions();
+ if (isset($options['lengths']) && count(array_filter($options['lengths'])) === 0) {
+ unset($options['lengths']);
+ }
+ if ($index->isPrimary()) {
+ if ($platform instanceof PostgreSqlPlatform) {
+ $name = $table->getName() . '_pkey';
+ } elseif ($platform instanceof AbstractMySQLPlatform) {
+ $name = 'PRIMARY';
+ } else {
+ $name = $index->getName();
+ }
+ } else {
+ $name = $index->getName();
+ }
+ if ($platform instanceof PostgreSqlPlatform) {
+ $name = strtolower($name);
+ }
+ $encoded['indexes'][$name] = [
+ 'name' => $name,
+ 'columns' => $index->getColumns(),
+ 'unique' => $index->isUnique(),
+ 'primary' => $index->isPrimary(),
+ 'flags' => $index->getFlags(),
+ 'options' => $options,
+ ];
+ }
+ ksort($encoded['indexes']);
+ return $encoded;
+ }
+}