diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2024-07-17 10:49:11 -0100 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2024-07-29 12:44:52 -0100 |
commit | 88cfab4f329c7ee1f360be1ad7d90cf767da3720 (patch) | |
tree | ad460429a5d58ef5ac67450cabc24e54bb9b1d9e | |
parent | 10821643645f1e9ab5d6d768bd52b37468722d3f (diff) | |
download | nextcloud-server-88cfab4f329c7ee1f360be1ad7d90cf767da3720.tar.gz nextcloud-server-88cfab4f329c7ee1f360be1ad7d90cf767da3720.zip |
feat(upgrade): migration attributes
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
d
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
f
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
d
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
20 files changed, 784 insertions, 0 deletions
diff --git a/core/Command/Db/Migrations/GenerateMetadataCommand.php b/core/Command/Db/Migrations/GenerateMetadataCommand.php new file mode 100644 index 00000000000..addcb59e68b --- /dev/null +++ b/core/Command/Db/Migrations/GenerateMetadataCommand.php @@ -0,0 +1,106 @@ +<?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\DB\Connection; +use OC\DB\MigrationService; +use OCP\App\IAppManager; +use ReflectionClass; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class GenerateMetadataCommand extends Command { + public function __construct( + private readonly Connection $connection, + private readonly IAppManager $appManager, + ) { + parent::__construct(); + } + + protected function configure() { + $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->extractMigrationAttributes('core'); + } + + /** + * get all apps and extract attributes + * + * @return array + * @throws \Exception + */ + private function extractMigrationMetadataFromApps(): array { + $allApps = \OC_App::getAllApps(); + $metadata = []; + foreach ($allApps as $appId) { + // We need to load app before being able to extract Migrations + // If app was not enabled before, we will disable it afterward. + $alreadyLoaded = $this->appManager->isInstalled($appId); + if (!$alreadyLoaded) { + $this->appManager->loadApp($appId); + } + $metadata[$appId] = $this->extractMigrationAttributes($appId); + if (!$alreadyLoaded) { + $this->appManager->disableApp($appId); + } + } + return $metadata; + } + + /** + * We get all migrations from an app, and for each migration we extract attributes + * + * @param string $appId + * + * @return array + * @throws \Exception + */ + private function extractMigrationAttributes(string $appId): array { + $ms = new MigrationService($appId, $this->connection); + + $metadata = []; + foreach($ms->getAvailableVersions() as $version) { + $metadata[$version] = []; + $class = new ReflectionClass($ms->createInstance($version)); + $attributes = $class->getAttributes(); + foreach ($attributes as $attribute) { + $metadata[$version][] = $attribute->newInstance(); + } + } + + return $metadata; + } +} diff --git a/core/Command/Db/Migrations/PreviewCommand.php b/core/Command/Db/Migrations/PreviewCommand.php new file mode 100644 index 00000000000..17d1d8b01ec --- /dev/null +++ b/core/Command/Db/Migrations/PreviewCommand.php @@ -0,0 +1,162 @@ +<?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\DB\Connection; +use OC\DB\MigrationService; +use OCP\Migration\Attributes\GenericMigrationAttribute; +use OCP\Migration\Attributes\MigrationAttribute; +use OCP\Migration\Exceptions\AttributeException; +use Psr\Log\LoggerInterface; +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; + +class PreviewCommand extends Command { + public function __construct( + private readonly Connection $connection, + private readonly LoggerInterface $logger, + ) { + parent::__construct(); + } + + protected function configure() { + $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'); + + $metadata = $this->getMetadata($version); + $parsed = $this->getMigrationsAttributes($metadata); + + $table = new Table($output); + $this->displayMigrations($table, 'core', $parsed['core']); + + $table->render(); + + return 0; + } + + private function displayMigrations(Table $table, string $appId, array $data): void { + $done = $this->getDoneMigrations($appId); + $done = array_diff($done, ['30000Date20240429122720']); + + $table->addRow( + [ + new TableCell( + $appId, + [ + 'colspan' => 2, + 'style' => new TableCellStyle(['cellFormat' => '<info>%s</info>']) + ] + ) + ] + )->addRow(new TableSeparator()); + + foreach($data as $migration => $attributes) { + if (in_array($migration, $done)) { + continue; + } + + $attributesStr = []; + /** @var MigrationAttribute[] $attributes */ + 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)]); + } + + } + + + + + + private function getMetadata(string $version): array { + $metadata = json_decode(file_get_contents('/tmp/nextcloud-' . $version . '.metadata'), true); + if (!$metadata) { + throw new \Exception(); + } + return $metadata['migrations'] ?? []; + } + + private function getDoneMigrations(string $appId): array { + $ms = new MigrationService($appId, $this->connection); + return $ms->getMigratedVersions(); + } + + private function getMigrationsAttributes(array $metadata): array { + $appsAttributes = []; + foreach (array_keys($metadata['apps']) as $appId) { + $appsAttributes[$appId] = $this->parseMigrations($metadata['apps'][$appId] ?? []); + } + + return [ + 'core' => $this->parseMigrations($metadata['core'] ?? []), + 'apps' => $appsAttributes + ]; + } + + private function parseMigrations(array $migrations): array { + $parsed = []; + foreach (array_keys($migrations) as $entry) { + $items = $migrations[$entry]; + $parsed[$entry] = []; + foreach ($items as $item) { + try { + $parsed[$entry][] = $this->createAttribute($item); + } catch (AttributeException $e) { + $this->logger->warning( + 'exception while trying to create attribute', + ['exception' => $e, 'item' => json_encode($item)] + ); + $parsed[$entry][] = new GenericMigrationAttribute($item); + } + } + } + + return $parsed; + } + + /** + * @param array $item + * + * @return MigrationAttribute|null + * @throws AttributeException + */ + private function createAttribute(array $item): ?MigrationAttribute { + $class = $item['class'] ?? ''; + $namespace = 'OCP\Migration\Attributes\\'; + if (!str_starts_with($class, $namespace) + || !ctype_alpha(substr($class, strlen($namespace)))) { + throw new AttributeException('class name does not looks valid'); + } + + try { + $attribute = new $class(); + return $attribute->import($item); + } catch (\Error) { + throw new AttributeException('cannot import Attribute'); + } + } +} diff --git a/core/register_command.php b/core/register_command.php index 6e89568cf9b..2922e98671a 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -67,6 +67,8 @@ if ($config->getSystemValueBool('installed', false)) { $application->add(Server::get(Command\Db\ExpectedSchema::class)); $application->add(Server::get(Command\Db\ExportSchema::class)); + $application->add(Server::get(Command\Db\Migrations\GenerateMetadataCommand::class)); + $application->add(Server::get(Command\Db\Migrations\PreviewCommand::class)); if ($config->getSystemValueBool('debug', false)) { $application->add(Server::get(Command\Db\Migrations\StatusCommand::class)); $application->add(Server::get(Command\Db\Migrations\MigrateCommand::class)); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index dc0c8f0e6c6..a167eba878f 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -572,6 +572,7 @@ return array( 'OCP\\Mail\\IEMailTemplate' => $baseDir . '/lib/public/Mail/IEMailTemplate.php', 'OCP\\Mail\\IMailer' => $baseDir . '/lib/public/Mail/IMailer.php', 'OCP\\Mail\\IMessage' => $baseDir . '/lib/public/Mail/IMessage.php', +<<<<<<< HEAD 'OCP\\Mail\\Provider\\Address' => $baseDir . '/lib/public/Mail/Provider/Address.php', 'OCP\\Mail\\Provider\\Attachment' => $baseDir . '/lib/public/Mail/Provider/Attachment.php', 'OCP\\Mail\\Provider\\Exception\\Exception' => $baseDir . '/lib/public/Mail/Provider/Exception/Exception.php', @@ -584,7 +585,24 @@ return array( 'OCP\\Mail\\Provider\\IProvider' => $baseDir . '/lib/public/Mail/Provider/IProvider.php', 'OCP\\Mail\\Provider\\IService' => $baseDir . '/lib/public/Mail/Provider/IService.php', 'OCP\\Mail\\Provider\\Message' => $baseDir . '/lib/public/Mail/Provider/Message.php', +======= + 'OCP\\Migration\\Attributes\\AddColumn' => $baseDir . '/lib/public/Migration/Attributes/AddColumn.php', + 'OCP\\Migration\\Attributes\\AddIndex' => $baseDir . '/lib/public/Migration/Attributes/AddIndex.php', + 'OCP\\Migration\\Attributes\\ColumnMigrationAttribute' => $baseDir . '/lib/public/Migration/Attributes/ColumnMigrationAttribute.php', + 'OCP\\Migration\\Attributes\\ColumnType' => $baseDir . '/lib/public/Migration/Attributes/ColumnType.php', + 'OCP\\Migration\\Attributes\\CreateTable' => $baseDir . '/lib/public/Migration/Attributes/CreateTable.php', + 'OCP\\Migration\\Attributes\\DeleteTable' => $baseDir . '/lib/public/Migration/Attributes/DeleteTable.php', + 'OCP\\Migration\\Attributes\\DropColumn' => $baseDir . '/lib/public/Migration/Attributes/DropColumn.php', + 'OCP\\Migration\\Attributes\\DropIndex' => $baseDir . '/lib/public/Migration/Attributes/DropIndex.php', + 'OCP\\Migration\\Attributes\\GenericMigrationAttribute' => $baseDir . '/lib/public/Migration/Attributes/GenericMigrationAttribute.php', + 'OCP\\Migration\\Attributes\\IndexMigrationAttribute' => $baseDir . '/lib/public/Migration/Attributes/IndexMigrationAttribute.php', + 'OCP\\Migration\\Attributes\\IndexType' => $baseDir . '/lib/public/Migration/Attributes/IndexType.php', + 'OCP\\Migration\\Attributes\\MigrationAttribute' => $baseDir . '/lib/public/Migration/Attributes/MigrationAttribute.php', + 'OCP\\Migration\\Attributes\\ModifyColumn' => $baseDir . '/lib/public/Migration/Attributes/ModifyColumn.php', + 'OCP\\Migration\\Attributes\\TableMigrationAttribute' => $baseDir . '/lib/public/Migration/Attributes/TableMigrationAttribute.php', +>>>>>>> 2f771df35a9 (feat(upgrade): migration attributes) 'OCP\\Migration\\BigIntMigration' => $baseDir . '/lib/public/Migration/BigIntMigration.php', + 'OCP\\Migration\\Exceptions\\AttributeException' => $baseDir . '/lib/public/Migration/Exceptions/AttributeException.php', 'OCP\\Migration\\IMigrationStep' => $baseDir . '/lib/public/Migration/IMigrationStep.php', 'OCP\\Migration\\IOutput' => $baseDir . '/lib/public/Migration/IOutput.php', 'OCP\\Migration\\IRepairStep' => $baseDir . '/lib/public/Migration/IRepairStep.php', @@ -1143,7 +1161,9 @@ return array( 'OC\\Core\\Command\\Db\\ExportSchema' => $baseDir . '/core/Command/Db/ExportSchema.php', 'OC\\Core\\Command\\Db\\Migrations\\ExecuteCommand' => $baseDir . '/core/Command/Db/Migrations/ExecuteCommand.php', 'OC\\Core\\Command\\Db\\Migrations\\GenerateCommand' => $baseDir . '/core/Command/Db/Migrations/GenerateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\GenerateMetadataCommand' => $baseDir . '/core/Command/Db/Migrations/GenerateMetadataCommand.php', 'OC\\Core\\Command\\Db\\Migrations\\MigrateCommand' => $baseDir . '/core/Command/Db/Migrations/MigrateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\PreviewCommand' => $baseDir . '/core/Command/Db/Migrations/PreviewCommand.php', 'OC\\Core\\Command\\Db\\Migrations\\StatusCommand' => $baseDir . '/core/Command/Db/Migrations/StatusCommand.php', 'OC\\Core\\Command\\Db\\SchemaEncoder' => $baseDir . '/core/Command/Db/SchemaEncoder.php', 'OC\\Core\\Command\\Encryption\\ChangeKeyStorageRoot' => $baseDir . '/core/Command/Encryption/ChangeKeyStorageRoot.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 635854db4c6..b328d6ff5c9 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -605,6 +605,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Mail\\IEMailTemplate' => __DIR__ . '/../../..' . '/lib/public/Mail/IEMailTemplate.php', 'OCP\\Mail\\IMailer' => __DIR__ . '/../../..' . '/lib/public/Mail/IMailer.php', 'OCP\\Mail\\IMessage' => __DIR__ . '/../../..' . '/lib/public/Mail/IMessage.php', +<<<<<<< HEAD 'OCP\\Mail\\Provider\\Address' => __DIR__ . '/../../..' . '/lib/public/Mail/Provider/Address.php', 'OCP\\Mail\\Provider\\Attachment' => __DIR__ . '/../../..' . '/lib/public/Mail/Provider/Attachment.php', 'OCP\\Mail\\Provider\\Exception\\Exception' => __DIR__ . '/../../..' . '/lib/public/Mail/Provider/Exception/Exception.php', @@ -617,7 +618,24 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Mail\\Provider\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Mail/Provider/IProvider.php', 'OCP\\Mail\\Provider\\IService' => __DIR__ . '/../../..' . '/lib/public/Mail/Provider/IService.php', 'OCP\\Mail\\Provider\\Message' => __DIR__ . '/../../..' . '/lib/public/Mail/Provider/Message.php', +======= + 'OCP\\Migration\\Attributes\\AddColumn' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/AddColumn.php', + 'OCP\\Migration\\Attributes\\AddIndex' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/AddIndex.php', + 'OCP\\Migration\\Attributes\\ColumnMigrationAttribute' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/ColumnMigrationAttribute.php', + 'OCP\\Migration\\Attributes\\ColumnType' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/ColumnType.php', + 'OCP\\Migration\\Attributes\\CreateTable' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/CreateTable.php', + 'OCP\\Migration\\Attributes\\DeleteTable' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/DeleteTable.php', + 'OCP\\Migration\\Attributes\\DropColumn' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/DropColumn.php', + 'OCP\\Migration\\Attributes\\DropIndex' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/DropIndex.php', + 'OCP\\Migration\\Attributes\\GenericMigrationAttribute' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/GenericMigrationAttribute.php', + 'OCP\\Migration\\Attributes\\IndexMigrationAttribute' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/IndexMigrationAttribute.php', + 'OCP\\Migration\\Attributes\\IndexType' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/IndexType.php', + 'OCP\\Migration\\Attributes\\MigrationAttribute' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/MigrationAttribute.php', + 'OCP\\Migration\\Attributes\\ModifyColumn' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/ModifyColumn.php', + 'OCP\\Migration\\Attributes\\TableMigrationAttribute' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/TableMigrationAttribute.php', +>>>>>>> 2f771df35a9 (feat(upgrade): migration attributes) 'OCP\\Migration\\BigIntMigration' => __DIR__ . '/../../..' . '/lib/public/Migration/BigIntMigration.php', + 'OCP\\Migration\\Exceptions\\AttributeException' => __DIR__ . '/../../..' . '/lib/public/Migration/Exceptions/AttributeException.php', 'OCP\\Migration\\IMigrationStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IMigrationStep.php', 'OCP\\Migration\\IOutput' => __DIR__ . '/../../..' . '/lib/public/Migration/IOutput.php', 'OCP\\Migration\\IRepairStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IRepairStep.php', @@ -1176,7 +1194,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Command\\Db\\ExportSchema' => __DIR__ . '/../../..' . '/core/Command/Db/ExportSchema.php', 'OC\\Core\\Command\\Db\\Migrations\\ExecuteCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/ExecuteCommand.php', 'OC\\Core\\Command\\Db\\Migrations\\GenerateCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/GenerateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\GenerateMetadataCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/GenerateMetadataCommand.php', 'OC\\Core\\Command\\Db\\Migrations\\MigrateCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/MigrateCommand.php', + 'OC\\Core\\Command\\Db\\Migrations\\PreviewCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/PreviewCommand.php', 'OC\\Core\\Command\\Db\\Migrations\\StatusCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/StatusCommand.php', 'OC\\Core\\Command\\Db\\SchemaEncoder' => __DIR__ . '/../../..' . '/core/Command/Db/SchemaEncoder.php', 'OC\\Core\\Command\\Encryption\\ChangeKeyStorageRoot' => __DIR__ . '/../../..' . '/core/Command/Encryption/ChangeKeyStorageRoot.php', diff --git a/lib/public/Migration/Attributes/AddColumn.php b/lib/public/Migration/Attributes/AddColumn.php new file mode 100644 index 00000000000..aadf3263b31 --- /dev/null +++ b/lib/public/Migration/Attributes/AddColumn.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class AddColumn extends ColumnMigrationAttribute { + public function definition(): string { + $type = is_null($this->getType()) ? '' : ' (' . $this->getType()->value . ')'; + $table = empty($this->getTable()) ? '' : ' to table \'' . $this->getTable() . '\''; + return empty($this->getName()) ? + 'Addition of a new column' . $type . $table + : 'Addition of column \'' . $this->getName() . '\'' . $type . $table; + } +} diff --git a/lib/public/Migration/Attributes/AddIndex.php b/lib/public/Migration/Attributes/AddIndex.php new file mode 100644 index 00000000000..9c96cd49a12 --- /dev/null +++ b/lib/public/Migration/Attributes/AddIndex.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class AddIndex extends IndexMigrationAttribute { + public function definition(): string { + $type = is_null($this->getType()) ? '' : ' (' . $this->getType()->value . ')'; + $table = empty($this->getTable()) ? '' : ' to table \'' . $this->getTable() . '\''; + return 'Addition of a new index' . $type . $table; + } +} diff --git a/lib/public/Migration/Attributes/ColumnMigrationAttribute.php b/lib/public/Migration/Attributes/ColumnMigrationAttribute.php new file mode 100644 index 00000000000..6862f73e11d --- /dev/null +++ b/lib/public/Migration/Attributes/ColumnMigrationAttribute.php @@ -0,0 +1,58 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +class ColumnMigrationAttribute extends MigrationAttribute implements JsonSerializable { + public function __construct( + string $table = '', + private string $name = '', + private ?ColumnType $type = null, + string $description = '', + array $notes = [], + ) { + parent::__construct($table, $description, $notes); + } + + public function setName(string $name): self { + $this->name = $name; + return $this; + } + + public function getName(): string { + return $this->name; + } + + public function setType(?ColumnType $type): self { + $this->type = $type; + return $this; + } + + public function getType(): ?ColumnType { + return $this->type; + } + + public function import(array $data): self { + parent::import($data); + $this->setName($data['name'] ?? ''); + $this->setType(ColumnType::tryFrom($data['type'] ?? '')); + return $this; + } + + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'name' => $this->getName(), + 'type' => $this->getType() ?? '', + ] + ); + } +} diff --git a/lib/public/Migration/Attributes/ColumnType.php b/lib/public/Migration/Attributes/ColumnType.php new file mode 100644 index 00000000000..058b74f6516 --- /dev/null +++ b/lib/public/Migration/Attributes/ColumnType.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +/** + * enum FieldType based on OCP\DB\Types + * + * @see \OCP\DB\Types + * @since 30.0.0 + */ +enum ColumnType : string { + /** @since 30.0.0 */ + case BIGINT = 'bigint'; + /** @since 30.0.0 */ + case BINARY = 'binary'; + /** @since 30.0.0 */ + case BLOB = 'blob'; + /** @since 30.0.0 */ + case BOOLEAN = 'boolean'; + /** @since 30.0.0 */ + case DATE = 'date'; + /** @since 30.0.0 */ + case DATETIME = 'datetime'; + /** @since 30.0.0 */ + case DECIMAL = 'decimal'; + /** @since 30.0.0 */ + case FLOAT = 'float'; + /** @since 30.0.0 */ + case INTEGER = 'integer'; + /** @since 30.0.0 */ + case SMALLINT = 'smallint'; + /** @since 30.0.0 */ + case STRING = 'string'; + /** @since 30.0.0 */ + case TEXT = 'text'; + /** @since 30.0.0 */ + case TIME = 'time'; + /** @since 30.0.0 */ + case JSON = 'json'; +} diff --git a/lib/public/Migration/Attributes/CreateTable.php b/lib/public/Migration/Attributes/CreateTable.php new file mode 100644 index 00000000000..600d9bf4b9f --- /dev/null +++ b/lib/public/Migration/Attributes/CreateTable.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class CreateTable extends TableMigrationAttribute { + public function definition(): string { + $definition = empty($this->getTable()) ? 'Creation of a new table' : 'Creation of new table \'' . $this->getTable() . '\''; + $definition .= empty($this->getColumns()) ? '' : ' with columns ' . implode(', ', $this->getColumns()); + return $definition; + } +} diff --git a/lib/public/Migration/Attributes/DeleteTable.php b/lib/public/Migration/Attributes/DeleteTable.php new file mode 100644 index 00000000000..97bd36e385e --- /dev/null +++ b/lib/public/Migration/Attributes/DeleteTable.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class DeleteTable extends MigrationAttribute { + public function definition(): string { + return empty($this->getTable()) ? 'Deletion of a table' : 'Deletion of table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/DropColumn.php b/lib/public/Migration/Attributes/DropColumn.php new file mode 100644 index 00000000000..dfa1e81b315 --- /dev/null +++ b/lib/public/Migration/Attributes/DropColumn.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class DropColumn extends ColumnMigrationAttribute { + public function definition(): string { + $table = empty($this->getTable()) ? '' : ' from table \'' . $this->getTable() . '\''; + return empty($this->getName()) ? + 'Deletion of a column' . $table + : 'Deletion of column \'' . $this->getName() . '\'' . $table; + } +} diff --git a/lib/public/Migration/Attributes/DropIndex.php b/lib/public/Migration/Attributes/DropIndex.php new file mode 100644 index 00000000000..cbeea4f5997 --- /dev/null +++ b/lib/public/Migration/Attributes/DropIndex.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class DropIndex extends IndexMigrationAttribute { + public function definition(): string { + return empty($this->getTable()) ? + 'Deletion of an index' + : 'Deletion of an index from table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/GenericMigrationAttribute.php b/lib/public/Migration/Attributes/GenericMigrationAttribute.php new file mode 100644 index 00000000000..f0e3af97615 --- /dev/null +++ b/lib/public/Migration/Attributes/GenericMigrationAttribute.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +class GenericMigrationAttribute extends MigrationAttribute implements JsonSerializable { + public function __construct( + private readonly array $details = [] + ) { + parent::__construct( + $details['table'] ?? '', + $details['description'] ?? '', + $details['notes'] ?? [] + ); + } + + public function definition(): string { + return json_encode($this->jsonSerialize(), JSON_UNESCAPED_SLASHES); + } + + public function jsonSerialize(): array { + return $this->details; + } +} diff --git a/lib/public/Migration/Attributes/IndexMigrationAttribute.php b/lib/public/Migration/Attributes/IndexMigrationAttribute.php new file mode 100644 index 00000000000..fbe636bd6b0 --- /dev/null +++ b/lib/public/Migration/Attributes/IndexMigrationAttribute.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +class IndexMigrationAttribute extends MigrationAttribute implements JsonSerializable { + public function __construct( + string $table = '', + private ?IndexType $type = null, + string $description = '', + array $notes = [], + ) { + parent::__construct($table, $description, $notes); + } + + public function setType(?IndexType $type): self { + $this->type = $type; + return $this; + } + + public function getType(): ?IndexType { + return $this->type; + } + + public function import(array $data): self { + parent::import($data); + $this->setType(IndexType::tryFrom($data['type'] ?? '')); + return $this; + } + + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'type' => $this->getType() ?? '', + ] + ); + } +} diff --git a/lib/public/Migration/Attributes/IndexType.php b/lib/public/Migration/Attributes/IndexType.php new file mode 100644 index 00000000000..842f135e700 --- /dev/null +++ b/lib/public/Migration/Attributes/IndexType.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +/** + * @since 30.0.0 + */ +enum IndexType : string { + /** @since 30.0.0 */ + case PRIMARY = 'primary'; // migration is estimated to require few minutes + /** @since 30.0.0 */ + case INDEX = 'index'; // depends on setup, migration might require some time + /** @since 30.0.0 */ + case UNIQUE = 'unique'; // migration should be light and quick +} diff --git a/lib/public/Migration/Attributes/MigrationAttribute.php b/lib/public/Migration/Attributes/MigrationAttribute.php new file mode 100644 index 00000000000..9f88614e37d --- /dev/null +++ b/lib/public/Migration/Attributes/MigrationAttribute.php @@ -0,0 +1,66 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +class MigrationAttribute implements JsonSerializable { + public function __construct( + private string $table = '', + private string $description = '', + private array $notes = [], + ) { + } + + public function setTable(string $table): self { + $this->table = $table; + return $this; + } + + public function getTable(): string { + return $this->table; + } + + public function setDescription(string $description): self { + $this->description = $description; + return $this; + } + + public function getDescription(): string { + return $this->description; + } + + public function setNotes(array $notes): self { + $this->notes = $notes; + return $this; + } + + public function getNotes(): array { + return $this->notes; + } + + public function definition(): string { + return json_encode($this->jsonSerialize(), JSON_UNESCAPED_SLASHES); + } + + public function import(array $data): self { + return $this->setTable($data['table'] ?? '') + ->setDescription($data['description'] ?? '') + ->setNotes($data['notes'] ?? []); + } + + public function jsonSerialize(): array { + return [ + 'class' => get_class($this), + 'table' => $this->getTable(), + 'description' => $this->getDescription(), + 'notes' => $this->getNotes(), + ]; + } +} diff --git a/lib/public/Migration/Attributes/ModifyColumn.php b/lib/public/Migration/Attributes/ModifyColumn.php new file mode 100644 index 00000000000..e73b10478f7 --- /dev/null +++ b/lib/public/Migration/Attributes/ModifyColumn.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class ModifyColumn extends ColumnMigrationAttribute { + public function definition(): string { + $type = is_null($this->getType()) ? '' : ' to ' . $this->getType()->value; + $table = empty($this->getTable()) ? '' : ' from table \'' . $this->getTable() . '\''; + return empty($this->getName()) ? + 'Modification of a column' . $table . $type + : 'Modification of column \'' . $this->getName() . '\'' . $table . $type; + } +} diff --git a/lib/public/Migration/Attributes/TableMigrationAttribute.php b/lib/public/Migration/Attributes/TableMigrationAttribute.php new file mode 100644 index 00000000000..1c3f90c6acf --- /dev/null +++ b/lib/public/Migration/Attributes/TableMigrationAttribute.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +class TableMigrationAttribute extends MigrationAttribute implements JsonSerializable { + public function __construct( + string $table = '', + private array $columns = [], + string $description = '', + array $notes = [], + ) { + parent::__construct($table, $description, $notes); + } + + public function setColumns(array $columns): self { + $this->columns = $columns; + return $this; + } + + public function getColumns(): array { + return $this->columns; + } + + public function import(array $data): self { + parent::import($data); + $this->setColumns($data['columns'] ?? []); + return $this; + } + + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'columns' => $this->getColumns(), + ] + ); + } +} diff --git a/lib/public/Migration/Exceptions/AttributeException.php b/lib/public/Migration/Exceptions/AttributeException.php new file mode 100644 index 00000000000..92de70f3e96 --- /dev/null +++ b/lib/public/Migration/Exceptions/AttributeException.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Exceptions; + +use Exception; + +/** + * @since 30.0.0 + */ +class AttributeException extends Exception { +} |