aboutsummaryrefslogtreecommitdiffstats
path: root/core/Command/Db
diff options
context:
space:
mode:
Diffstat (limited to 'core/Command/Db')
-rw-r--r--core/Command/Db/AddMissingColumns.php96
-rw-r--r--core/Command/Db/AddMissingIndices.php458
-rw-r--r--core/Command/Db/AddMissingPrimaryKeys.php179
-rw-r--r--core/Command/Db/ConvertFilecacheBigInt.php60
-rw-r--r--core/Command/Db/ConvertMysqlToMB4.php54
-rw-r--r--core/Command/Db/ConvertType.php111
-rw-r--r--core/Command/Db/ExpectedSchema.php68
-rw-r--r--core/Command/Db/ExportSchema.php44
-rw-r--r--core/Command/Db/Migrations/ExecuteCommand.php45
-rw-r--r--core/Command/Db/Migrations/GenerateCommand.php88
-rw-r--r--core/Command/Db/Migrations/GenerateMetadataCommand.php79
-rw-r--r--core/Command/Db/Migrations/MigrateCommand.php35
-rw-r--r--core/Command/Db/Migrations/PreviewCommand.php111
-rw-r--r--core/Command/Db/Migrations/StatusCommand.php33
-rw-r--r--core/Command/Db/SchemaEncoder.php115
15 files changed, 669 insertions, 907 deletions
diff --git a/core/Command/Db/AddMissingColumns.php b/core/Command/Db/AddMissingColumns.php
index 1e338e151df..33b4b24a6cb 100644
--- a/core/Command/Db/AddMissingColumns.php
+++ b/core/Command/Db/AddMissingColumns.php
@@ -3,38 +3,19 @@
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;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
-use OCP\IDBConnection;
+use OCP\DB\Events\AddMissingColumnsEvent;
+use OCP\EventDispatcher\IEventDispatcher;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class AddMissingColumns
@@ -45,68 +26,53 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\Core\Command\Db
*/
class AddMissingColumns extends Command {
-
- /** @var Connection */
- private $connection;
-
- /** @var EventDispatcherInterface */
- private $dispatcher;
-
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(
+ private Connection $connection,
+ private IEventDispatcher $dispatcher,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->dispatcher = $dispatcher;
}
protected function configure() {
$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 {
- $this->addCoreColumns($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
// Dispatch event so apps can also update columns if needed
- $event = new GenericEvent($output);
- $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_COLUMNS_EVENT, $event);
- return 0;
- }
-
- /**
- * add missing indices to the share table
- *
- * @param OutputInterface $output
- * @param bool $dryRun If true, will return the sql queries instead of running them.
- * @throws \Doctrine\DBAL\Schema\SchemaException
- */
- private function addCoreColumns(OutputInterface $output, bool $dryRun): void {
- $output->writeln('<info>Check columns of the comments table.</info>');
-
- $schema = new SchemaWrapper($this->connection);
+ $event = new AddMissingColumnsEvent();
+ $this->dispatcher->dispatchTyped($event);
+ $missingColumns = $event->getMissingColumns();
$updated = false;
- if ($schema->hasTable('comments')) {
- $table = $schema->getTable('comments');
- if (!$table->hasColumn('reference_id')) {
- $output->writeln('<info>Adding additional reference_id column to the comments table, this can take some time...</info>');
- $table->addColumn('reference_id', 'string', [
- 'notnull' => false,
- 'length' => 64,
- ]);
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
+ if (!empty($missingColumns)) {
+ $schema = new SchemaWrapper($this->connection);
+
+ foreach ($missingColumns as $missingColumn) {
+ if ($schema->hasTable($missingColumn['tableName'])) {
+ $table = $schema->getTable($missingColumn['tableName']);
+ if (!$table->hasColumn($missingColumn['columnName'])) {
+ $output->writeln('<info>Adding additional ' . $missingColumn['columnName'] . ' column to the ' . $missingColumn['tableName'] . ' table, this can take some time...</info>');
+ $table->addColumn($missingColumn['columnName'], $missingColumn['typeName'], $missingColumn['options']);
+ $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
+ if ($dryRun && $sqlQueries !== null) {
+ $output->writeln($sqlQueries);
+ }
+ $updated = true;
+ $output->writeln('<info>' . $missingColumn['tableName'] . ' table updated successfully.</info>');
+ }
}
- $updated = true;
- $output->writeln('<info>Comments table updated successfully.</info>');
}
}
if (!$updated) {
$output->writeln('<info>Done.</info>');
}
+
+ return 0;
}
}
diff --git a/core/Command/Db/AddMissingIndices.php b/core/Command/Db/AddMissingIndices.php
index 8394e75e845..eec0aedce11 100644
--- a/core/Command/Db/AddMissingIndices.php
+++ b/core/Command/Db/AddMissingIndices.php
@@ -3,46 +3,19 @@
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;
-use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
-use OCP\IDBConnection;
+use OCP\DB\Events\AddMissingIndicesEvent;
+use OCP\EventDispatcher\IEventDispatcher;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class AddMissingIndices
@@ -53,403 +26,104 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\Core\Command\Db
*/
class AddMissingIndices extends Command {
-
- /** @var Connection */
- private $connection;
-
- /** @var EventDispatcherInterface */
- private $dispatcher;
-
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(
+ private Connection $connection,
+ private IEventDispatcher $dispatcher,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->dispatcher = $dispatcher;
}
protected function configure() {
$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 {
- $this->addCoreIndexes($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
// Dispatch event so apps can also update indexes if needed
- $event = new GenericEvent($output);
- $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_INDEXES_EVENT, $event);
- return 0;
- }
-
- /**
- * add missing indices to the share table
- *
- * @param OutputInterface $output
- * @param bool $dryRun If true, will return the sql queries instead of running them.
- * @throws \Doctrine\DBAL\Schema\SchemaException
- */
- private function addCoreIndexes(OutputInterface $output, bool $dryRun): void {
- $output->writeln('<info>Check indices of the share table.</info>');
-
- $schema = new SchemaWrapper($this->connection);
- $updated = false;
-
- if ($schema->hasTable('share')) {
- $table = $schema->getTable('share');
- if (!$table->hasIndex('share_with_index')) {
- $output->writeln('<info>Adding additional share_with index to the share table, this can take some time...</info>');
- $table->addIndex(['share_with'], 'share_with_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
-
- if (!$table->hasIndex('parent_index')) {
- $output->writeln('<info>Adding additional parent index to the share table, this can take some time...</info>');
- $table->addIndex(['parent'], 'parent_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
-
- if (!$table->hasIndex('owner_index')) {
- $output->writeln('<info>Adding additional owner index to the share table, this can take some time...</info>');
- $table->addIndex(['uid_owner'], 'owner_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
-
- if (!$table->hasIndex('initiator_index')) {
- $output->writeln('<info>Adding additional initiator index to the share table, this can take some time...</info>');
- $table->addIndex(['uid_initiator'], 'initiator_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the filecache table.</info>');
- if ($schema->hasTable('filecache')) {
- $table = $schema->getTable('filecache');
- if (!$table->hasIndex('fs_mtime')) {
- $output->writeln('<info>Adding additional mtime index to the filecache table, this can take some time...</info>');
- $table->addIndex(['mtime'], 'fs_mtime');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- if (!$table->hasIndex('fs_size')) {
- $output->writeln('<info>Adding additional size index to the filecache table, this can take some time...</info>');
- $table->addIndex(['size'], 'fs_size');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- if (!$table->hasIndex('fs_id_storage_size')) {
- $output->writeln('<info>Adding additional size index to the filecache table, this can take some time...</info>');
- $table->addIndex(['fileid', 'storage', 'size'], 'fs_id_storage_size');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- if (!$table->hasIndex('fs_storage_path_prefix') && !$schema->getDatabasePlatform() instanceof PostgreSQL94Platform) {
- $output->writeln('<info>Adding additional path index to the filecache table, this can take some time...</info>');
- $table->addIndex(['storage', 'path'], 'fs_storage_path_prefix', [], ['lengths' => [null, 64]]);
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the twofactor_providers table.</info>');
- if ($schema->hasTable('twofactor_providers')) {
- $table = $schema->getTable('twofactor_providers');
- if (!$table->hasIndex('twofactor_providers_uid')) {
- $output->writeln('<info>Adding additional twofactor_providers_uid index to the twofactor_providers table, this can take some time...</info>');
- $table->addIndex(['uid'], 'twofactor_providers_uid');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Twofactor_providers table updated successfully.</info>');
- }
- }
+ $event = new AddMissingIndicesEvent();
+ $this->dispatcher->dispatchTyped($event);
+
+ $missingIndices = $event->getMissingIndices();
+ $toReplaceIndices = $event->getIndicesToReplace();
+
+ if ($missingIndices !== [] || $toReplaceIndices !== []) {
+ $schema = new SchemaWrapper($this->connection);
+
+ foreach ($missingIndices as $missingIndex) {
+ if ($schema->hasTable($missingIndex['tableName'])) {
+ $table = $schema->getTable($missingIndex['tableName']);
+ if (!$table->hasIndex($missingIndex['indexName'])) {
+ $output->writeln('<info>Adding additional ' . $missingIndex['indexName'] . ' index to the ' . $table->getName() . ' table, this can take some time...</info>');
+
+ if ($missingIndex['dropUnnamedIndex']) {
+ foreach ($table->getIndexes() as $index) {
+ $columns = $index->getColumns();
+ if ($columns === $missingIndex['columns']) {
+ $table->dropIndex($index->getName());
+ }
+ }
+ }
- $output->writeln('<info>Check indices of the login_flow_v2 table.</info>');
- if ($schema->hasTable('login_flow_v2')) {
- $table = $schema->getTable('login_flow_v2');
- if (!$table->hasIndex('poll_token')) {
- $output->writeln('<info>Adding additional indeces to the login_flow_v2 table, this can take some time...</info>');
+ if ($missingIndex['uniqueIndex']) {
+ $table->addUniqueIndex($missingIndex['columns'], $missingIndex['indexName'], $missingIndex['options']);
+ } else {
+ $table->addIndex($missingIndex['columns'], $missingIndex['indexName'], [], $missingIndex['options']);
+ }
- foreach ($table->getIndexes() as $index) {
- $columns = $index->getColumns();
- if ($columns === ['poll_token'] ||
- $columns === ['login_token'] ||
- $columns === ['timestamp']) {
- $table->dropIndex($index->getName());
+ if (!$dryRun) {
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
+ }
+ $output->writeln('<info>' . $table->getName() . ' table updated successfully.</info>');
}
}
-
- $table->addUniqueIndex(['poll_token'], 'poll_token');
- $table->addUniqueIndex(['login_token'], 'login_token');
- $table->addIndex(['timestamp'], 'timestamp');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>login_flow_v2 table updated successfully.</info>');
}
- }
- $output->writeln('<info>Check indices of the whats_new table.</info>');
- if ($schema->hasTable('whats_new')) {
- $table = $schema->getTable('whats_new');
- if (!$table->hasIndex('version')) {
- $output->writeln('<info>Adding version index to the whats_new table, this can take some time...</info>');
+ foreach ($toReplaceIndices as $toReplaceIndex) {
+ if ($schema->hasTable($toReplaceIndex['tableName'])) {
+ $table = $schema->getTable($toReplaceIndex['tableName']);
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['version']) {
- $table->dropIndex($index->getName());
+ if ($table->hasIndex($toReplaceIndex['newIndexName'])) {
+ continue;
}
- }
-
- $table->addUniqueIndex(['version'], 'version');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>whats_new table updated successfully.</info>');
- }
- }
- $output->writeln('<info>Check indices of the cards table.</info>');
- $cardsUpdated = false;
- if ($schema->hasTable('cards')) {
- $table = $schema->getTable('cards');
+ $output->writeln('<info>Adding additional ' . $toReplaceIndex['newIndexName'] . ' index to the ' . $table->getName() . ' table, this can take some time...</info>');
- if ($table->hasIndex('addressbookid_uri_index')) {
- if ($table->hasIndex('cards_abiduri')) {
- $table->dropIndex('addressbookid_uri_index');
- } else {
- $output->writeln('<info>Renaming addressbookid_uri_index index to cards_abiduri in the cards table, this can take some time...</info>');
-
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid', 'uri']) {
- $table->renameIndex('addressbookid_uri_index', 'cards_abiduri');
- }
+ if ($toReplaceIndex['uniqueIndex']) {
+ $table->addUniqueIndex($toReplaceIndex['columns'], $toReplaceIndex['newIndexName'], $toReplaceIndex['options']);
+ } else {
+ $table->addIndex($toReplaceIndex['columns'], $toReplaceIndex['newIndexName'], [], $toReplaceIndex['options']);
}
- }
-
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $cardsUpdated = true;
- }
- if (!$table->hasIndex('cards_abid')) {
- $output->writeln('<info>Adding cards_abid index to the cards table, this can take some time...</info>');
-
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid']) {
- $table->dropIndex($index->getName());
+ if (!$dryRun) {
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
}
- }
- $table->addIndex(['addressbookid'], 'cards_abid');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $cardsUpdated = true;
- }
-
- if (!$table->hasIndex('cards_abiduri')) {
- $output->writeln('<info>Adding cards_abiduri index to the cards table, this can take some time...</info>');
-
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid', 'uri']) {
- $table->dropIndex($index->getName());
+ foreach ($toReplaceIndex['oldIndexNames'] as $oldIndexName) {
+ if ($table->hasIndex($oldIndexName)) {
+ $output->writeln('<info>Removing ' . $oldIndexName . ' index from the ' . $table->getName() . ' table</info>');
+ $table->dropIndex($oldIndexName);
+ }
}
- }
-
- $table->addIndex(['addressbookid', 'uri'], 'cards_abiduri');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $cardsUpdated = true;
- }
-
- if ($cardsUpdated) {
- $updated = true;
- $output->writeln('<info>cards table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the cards_properties table.</info>');
- if ($schema->hasTable('cards_properties')) {
- $table = $schema->getTable('cards_properties');
- if (!$table->hasIndex('cards_prop_abid')) {
- $output->writeln('<info>Adding cards_prop_abid index to the cards_properties table, this can take some time...</info>');
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid']) {
- $table->dropIndex($index->getName());
+ if (!$dryRun) {
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
}
+ $output->writeln('<info>' . $table->getName() . ' table updated successfully.</info>');
}
-
- $table->addIndex(['addressbookid'], 'cards_prop_abid');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>cards_properties table updated successfully.</info>');
}
- }
- $output->writeln('<info>Check indices of the calendarobjects_props table.</info>');
- if ($schema->hasTable('calendarobjects_props')) {
- $table = $schema->getTable('calendarobjects_props');
- if (!$table->hasIndex('calendarobject_calid_index')) {
- $output->writeln('<info>Adding calendarobject_calid_index index to the calendarobjects_props table, this can take some time...</info>');
-
- $table->addIndex(['calendarid', 'calendartype'], 'calendarobject_calid_index');
+ if ($dryRun) {
$sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
+ if ($sqlQueries !== null) {
$output->writeln($sqlQueries);
}
- $updated = true;
- $output->writeln('<info>calendarobjects_props table updated successfully.</info>');
}
}
- $output->writeln('<info>Check indices of the schedulingobjects table.</info>');
- if ($schema->hasTable('schedulingobjects')) {
- $table = $schema->getTable('schedulingobjects');
- if (!$table->hasIndex('schedulobj_principuri_index')) {
- $output->writeln('<info>Adding schedulobj_principuri_index index to the schedulingobjects table, this can take some time...</info>');
-
- $table->addIndex(['principaluri'], 'schedulobj_principuri_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>schedulingobjects table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_properties table.</info>');
- if ($schema->hasTable('properties')) {
- $table = $schema->getTable('properties');
- $propertiesUpdated = false;
-
- if (!$table->hasIndex('properties_path_index')) {
- $output->writeln('<info>Adding properties_path_index index to the oc_properties table, this can take some time...</info>');
-
- $table->addIndex(['userid', 'propertypath'], 'properties_path_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $propertiesUpdated = true;
- }
- if (!$table->hasIndex('properties_pathonly_index')) {
- $output->writeln('<info>Adding properties_pathonly_index index to the oc_properties table, this can take some time...</info>');
-
- $table->addIndex(['propertypath'], 'properties_pathonly_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $propertiesUpdated = true;
- }
-
- if ($propertiesUpdated) {
- $updated = true;
- $output->writeln('<info>oc_properties table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_jobs table.</info>');
- if ($schema->hasTable('jobs')) {
- $table = $schema->getTable('jobs');
- if (!$table->hasIndex('job_lastcheck_reserved')) {
- $output->writeln('<info>Adding job_lastcheck_reserved index to the oc_jobs table, this can take some time...</info>');
-
- $table->addIndex(['last_checked', 'reserved_at'], 'job_lastcheck_reserved');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>oc_properties table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_direct_edit table.</info>');
- if ($schema->hasTable('direct_edit')) {
- $table = $schema->getTable('direct_edit');
- if (!$table->hasIndex('direct_edit_timestamp')) {
- $output->writeln('<info>Adding direct_edit_timestamp index to the oc_direct_edit table, this can take some time...</info>');
-
- $table->addIndex(['timestamp'], 'direct_edit_timestamp');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>oc_direct_edit table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_preferences table.</info>');
- if ($schema->hasTable('preferences')) {
- $table = $schema->getTable('preferences');
- if (!$table->hasIndex('preferences_app_key')) {
- $output->writeln('<info>Adding preferences_app_key index to the oc_preferences table, this can take some time...</info>');
-
- $table->addIndex(['appid', 'configkey'], 'preferences_app_key');
- $this->connection->migrateToSchema($schema->getWrappedSchema());
- $updated = true;
- $output->writeln('<info>oc_properties table updated successfully.</info>');
- }
- }
-
- if (!$updated) {
- $output->writeln('<info>Done.</info>');
- }
+ return 0;
}
}
diff --git a/core/Command/Db/AddMissingPrimaryKeys.php b/core/Command/Db/AddMissingPrimaryKeys.php
index cbb99a69251..1eb11c894fa 100644
--- a/core/Command/Db/AddMissingPrimaryKeys.php
+++ b/core/Command/Db/AddMissingPrimaryKeys.php
@@ -3,38 +3,19 @@
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;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
-use OCP\IDBConnection;
+use OCP\DB\Events\AddMissingPrimaryKeyEvent;
+use OCP\EventDispatcher\IEventDispatcher;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class AddMissingPrimaryKeys
@@ -45,153 +26,59 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\Core\Command\Db
*/
class AddMissingPrimaryKeys extends Command {
-
- /** @var Connection */
- private $connection;
-
- /** @var EventDispatcherInterface */
- private $dispatcher;
-
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(
+ private Connection $connection,
+ private IEventDispatcher $dispatcher,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->dispatcher = $dispatcher;
}
protected function configure() {
$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 {
- $this->addCorePrimaryKeys($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
// Dispatch event so apps can also update indexes if needed
- $event = new GenericEvent($output);
- $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_PRIMARY_KEYS_EVENT, $event);
- return 0;
- }
-
- /**
- * add missing indices to the share table
- *
- * @param OutputInterface $output
- * @param bool $dryRun If true, will return the sql queries instead of running them.
- * @throws \Doctrine\DBAL\Schema\SchemaException
- */
- private function addCorePrimaryKeys(OutputInterface $output, bool $dryRun): void {
- $output->writeln('<info>Check primary keys.</info>');
-
- $schema = new SchemaWrapper($this->connection);
+ $event = new AddMissingPrimaryKeyEvent();
+ $this->dispatcher->dispatchTyped($event);
+ $missingKeys = $event->getMissingPrimaryKeys();
$updated = false;
- if ($schema->hasTable('federated_reshares')) {
- $table = $schema->getTable('federated_reshares');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the federated_reshares table, this can take some time...</info>');
- $table->setPrimaryKey(['share_id'], 'federated_res_pk');
- if ($table->hasIndex('share_id_index')) {
- $table->dropIndex('share_id_index');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>federated_reshares table updated successfully.</info>');
- }
- }
-
- if ($schema->hasTable('systemtag_object_mapping')) {
- $table = $schema->getTable('systemtag_object_mapping');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the systemtag_object_mapping table, this can take some time...</info>');
- $table->setPrimaryKey(['objecttype', 'objectid', 'systemtagid'], 'som_pk');
- if ($table->hasIndex('mapping')) {
- $table->dropIndex('mapping');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>systemtag_object_mapping table updated successfully.</info>');
- }
- }
+ if (!empty($missingKeys)) {
+ $schema = new SchemaWrapper($this->connection);
- if ($schema->hasTable('comments_read_markers')) {
- $table = $schema->getTable('comments_read_markers');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the comments_read_markers table, this can take some time...</info>');
- $table->setPrimaryKey(['user_id', 'object_type', 'object_id'], 'crm_pk');
- if ($table->hasIndex('comments_marker_index')) {
- $table->dropIndex('comments_marker_index');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>comments_read_markers table updated successfully.</info>');
- }
- }
+ foreach ($missingKeys as $missingKey) {
+ if ($schema->hasTable($missingKey['tableName'])) {
+ $table = $schema->getTable($missingKey['tableName']);
+ if (!$table->hasPrimaryKey()) {
+ $output->writeln('<info>Adding primary key to the ' . $missingKey['tableName'] . ' table, this can take some time...</info>');
+ $table->setPrimaryKey($missingKey['columns'], $missingKey['primaryKeyName']);
- if ($schema->hasTable('collres_resources')) {
- $table = $schema->getTable('collres_resources');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the collres_resources table, this can take some time...</info>');
- $table->setPrimaryKey(['collection_id', 'resource_type', 'resource_id'], 'crr_pk');
- if ($table->hasIndex('collres_unique_res')) {
- $table->dropIndex('collres_unique_res');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>collres_resources table updated successfully.</info>');
- }
- }
+ if ($missingKey['formerIndex'] && $table->hasIndex($missingKey['formerIndex'])) {
+ $table->dropIndex($missingKey['formerIndex']);
+ }
- if ($schema->hasTable('collres_accesscache')) {
- $table = $schema->getTable('collres_accesscache');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the collres_accesscache table, this can take some time...</info>');
- $table->setPrimaryKey(['user_id', 'collection_id', 'resource_type', 'resource_id'], 'cra_pk');
- if ($table->hasIndex('collres_unique_user')) {
- $table->dropIndex('collres_unique_user');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>collres_accesscache table updated successfully.</info>');
- }
- }
+ $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
+ if ($dryRun && $sqlQueries !== null) {
+ $output->writeln($sqlQueries);
+ }
- if ($schema->hasTable('filecache_extended')) {
- $table = $schema->getTable('filecache_extended');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the filecache_extended table, this can take some time...</info>');
- $table->setPrimaryKey(['fileid'], 'fce_pk');
- if ($table->hasIndex('fce_fileid_idx')) {
- $table->dropIndex('fce_fileid_idx');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
+ $updated = true;
+ $output->writeln('<info>' . $missingKey['tableName'] . ' table updated successfully.</info>');
+ }
}
- $updated = true;
- $output->writeln('<info>filecache_extended table updated successfully.</info>');
}
}
if (!$updated) {
$output->writeln('<info>Done.</info>');
}
+
+ return 0;
}
}
diff --git a/core/Command/Db/ConvertFilecacheBigInt.php b/core/Command/Db/ConvertFilecacheBigInt.php
index 34b717025a0..0d96d139701 100644
--- a/core/Command/Db/ConvertFilecacheBigInt.php
+++ b/core/Command/Db/ConvertFilecacheBigInt.php
@@ -1,56 +1,26 @@
<?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 OCP\DB\Types;
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;
class ConvertFilecacheBigInt extends Command {
-
- /** @var Connection */
- private $connection;
-
- /**
- * @param Connection $connection
- */
- public function __construct(Connection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private Connection $connection,
+ ) {
parent::__construct();
}
@@ -60,8 +30,10 @@ class ConvertFilecacheBigInt extends Command {
->setDescription('Convert the ID columns of the filecache to BigInt');
}
- protected function getColumnsByTable() {
- // also update in CheckSetupController::hasBigIntConversionPendingColumns()
+ /**
+ * @return array<string,string[]>
+ */
+ public static function getColumnsByTable(): array {
return [
'activity' => ['activity_id', 'object_id'],
'activity_mq' => ['mail_id'],
@@ -72,6 +44,7 @@ class ConvertFilecacheBigInt extends Command {
'filecache_extended' => ['fileid'],
'files_trash' => ['auto_id'],
'file_locks' => ['id'],
+ 'file_metadata' => ['id'],
'jobs' => ['id'],
'mimetypes' => ['id'],
'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
@@ -82,10 +55,10 @@ 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 = $this->getColumnsByTable();
+ $tables = static::getColumnsByTable();
foreach ($tables as $tableName => $columns) {
if (!$schema->hasTable($tableName)) {
continue;
@@ -118,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 28f7ae6b2fa..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;
@@ -37,27 +18,12 @@ 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;
-
- private LoggerInterface $logger;
-
public function __construct(
- IConfig $config,
- IDBConnection $connection,
- IURLGenerator $urlGenerator,
- LoggerInterface $logger
+ private IConfig $config,
+ private IDBConnection $connection,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger,
) {
- $this->config = $config;
- $this->connection = $connection;
- $this->urlGenerator = $urlGenerator;
- $this->logger = $logger;
parent::__construct();
}
@@ -68,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 754cbd08812..0067bec4d9e 100644
--- a/core/Command/Db/ConvertType.php
+++ b/core/Command/Db/ConvertType.php
@@ -1,46 +1,24 @@
<?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;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\Table;
-use OCP\DB\Types;
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;
@@ -56,26 +34,13 @@ use function preg_match;
use function preg_quote;
class ConvertType extends Command implements CompletionAwareInterface {
- /**
- * @var \OCP\IConfig
- */
- protected $config;
-
- /**
- * @var \OC\DB\ConnectionFactory
- */
- protected $connectionFactory;
-
- /** @var array */
- protected $columnTypes;
+ protected array $columnTypes = [];
- /**
- * @param \OCP\IConfig $config
- * @param \OC\DB\ConnectionFactory $connectionFactory
- */
- public function __construct(IConfig $config, ConnectionFactory $connectionFactory) {
- $this->config = $config;
- $this->connectionFactory = $connectionFactory;
+ public function __construct(
+ protected IConfig $config,
+ protected ConnectionFactory $connectionFactory,
+ protected IAppManager $appManager,
+ ) {
parent::__construct();
}
@@ -181,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;
}
@@ -195,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')) {
@@ -213,7 +181,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$output->writeln('<comment>The following tables will not be converted:</comment>');
$output->writeln($extraFromTables);
if (!$input->getOption('all-apps')) {
- $output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
+ $output->writeln('<comment>Please note that tables belonging to disabled (but not removed) apps</comment>');
$output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
}
@@ -242,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') {
@@ -258,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);
}
@@ -277,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);
}
}
@@ -290,7 +275,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
}
return preg_match($filterExpression, $asset) !== false;
});
- return $db->getSchemaManager()->listTableNames();
+ return $db->createSchemaManager()->listTableNames();
}
/**
@@ -312,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();
@@ -351,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();
@@ -418,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
@@ -441,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 6f4559ee7b4..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;
@@ -35,23 +19,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExecuteCommand extends Command implements CompletionAwareInterface {
-
- /** @var Connection */
- private $connection;
-
- /** @var IConfig */
- private $config;
-
- /**
- * ExecuteCommand constructor.
- *
- * @param Connection $connection
- * @param IConfig $config
- */
- public function __construct(Connection $connection, IConfig $config) {
- $this->connection = $connection;
- $this->config = $config;
-
+ public function __construct(
+ private Connection $connection,
+ private IConfig $config,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Db/Migrations/GenerateCommand.php b/core/Command/Db/Migrations/GenerateCommand.php
index 9790a96a0fd..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}} Your name <your@email.com>
- *
- * @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,54 +39,48 @@ use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
+use Override;
/**
- * Auto-generated migration step: Please modify to your needs!
+ * FIXME Auto-generated migration step: Please modify to your needs!
*/
class {{classname}} extends SimpleMigrationStep {
/**
* @param IOutput $output
- * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
+ #[Override]
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
/**
* @param IOutput $output
- * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
+ #[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
{{schemabody}}
}
/**
* @param IOutput $output
- * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
+ #[Override]
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
}
';
- /** @var Connection */
- protected $connection;
-
- /** @var IAppManager */
- protected $appManager;
-
- /**
- * @param Connection $connection
- * @param 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();
}
@@ -154,7 +115,7 @@ class {{classname}} extends SimpleMigrationStep {
if ($fullVersion) {
[$major, $minor] = explode('.', $fullVersion);
- $shouldVersion = (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>');
@@ -162,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);
@@ -197,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));
}
@@ -242,7 +204,7 @@ class {{classname}} extends SimpleMigrationStep {
$path = $dir . '/' . $className . '.php';
if (file_put_contents($path, $code) === false) {
- throw new RuntimeException('Failed to generate new migration step.');
+ throw new RuntimeException('Failed to generate new migration step. Could not write to ' . $path);
}
return $path;
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 26f350cdae6..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;
@@ -33,15 +18,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MigrateCommand extends Command implements CompletionAwareInterface {
-
- /** @var Connection */
- private $connection;
-
- /**
- * @param Connection $connection
- */
- public function __construct(Connection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private Connection $connection,
+ ) {
parent::__construct();
}
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 fabaabe0a3e..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;
@@ -34,12 +18,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class StatusCommand extends Command implements CompletionAwareInterface {
-
- /** @var Connection */
- private $connection;
-
- public function __construct(Connection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private Connection $connection,
+ ) {
parent::__construct();
}
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;
+ }
+}