aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaxence Lange <maxence@artificial-owl.com>2024-08-02 01:43:04 -0100
committerMaxence Lange <maxence@artificial-owl.com>2024-08-06 22:41:35 -0100
commite227d5afa1e9c71062133a13bec8ac051a9ec5b7 (patch)
tree7bc29cdcd30b759e68a90726b9610d41ede00e63
parent89f82fd5378e2f4e53966ce25fbc8a4f02386de6 (diff)
downloadnextcloud-server-e227d5afa1e9c71062133a13bec8ac051a9ec5b7.tar.gz
nextcloud-server-e227d5afa1e9c71062133a13bec8ac051a9ec5b7.zip
feat(upgrade): migration attributes
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
-rw-r--r--core/Command/Db/Migrations/PreviewCommand.php102
-rw-r--r--core/register_command.php1
-rw-r--r--lib/composer/composer/autoload_classmap.php17
-rw-r--r--lib/composer/composer/autoload_static.php17
-rw-r--r--lib/private/Migration/Exceptions/AttributeException.php17
-rw-r--r--lib/private/Migration/MetadataManager.php127
-rw-r--r--lib/private/Updater/Exceptions/ReleaseMetadataException.php17
-rw-r--r--lib/private/Updater/ReleaseMetadata.php79
-rw-r--r--lib/public/Migration/Attributes/AddColumn.php29
-rw-r--r--lib/public/Migration/Attributes/AddIndex.php27
-rw-r--r--lib/public/Migration/Attributes/ColumnMigrationAttribute.php78
-rw-r--r--lib/public/Migration/Attributes/CreateTable.php29
-rw-r--r--lib/public/Migration/Attributes/DropColumn.php29
-rw-r--r--lib/public/Migration/Attributes/DropIndex.php27
-rw-r--r--lib/public/Migration/Attributes/DropTable.php27
-rw-r--r--lib/public/Migration/Attributes/GenericMigrationAttribute.php49
-rw-r--r--lib/public/Migration/Attributes/IndexMigrationAttribute.php32
-rw-r--r--lib/public/Migration/Attributes/MigrationAttribute.php118
-rw-r--r--lib/public/Migration/Attributes/ModifyColumn.php29
-rw-r--r--lib/public/Migration/Attributes/TableMigrationAttribute.php78
-rw-r--r--tests/lib/Updater/ReleaseMetadataTest.php209
21 files changed, 1138 insertions, 0 deletions
diff --git a/core/Command/Db/Migrations/PreviewCommand.php b/core/Command/Db/Migrations/PreviewCommand.php
new file mode 100644
index 00000000000..6440ed5835c
--- /dev/null
+++ b/core/Command/Db/Migrations/PreviewCommand.php
@@ -0,0 +1,102 @@
+<?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 MetadataManager $metadataManager,
+ private 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();
+
+ 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 = [];
+ 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/register_command.php b/core/register_command.php
index 4a84e551ce0..6d1abb52fea 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -110,6 +110,7 @@ if ($config->getSystemValueBool('installed', false)) {
$application->add(Server::get(Command\Db\AddMissingIndices::class));
$application->add(Server::get(Command\Db\AddMissingPrimaryKeys::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 6defb0f12fb..a09bbb25038 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -555,6 +555,18 @@ 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',
+ '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\\CreateTable' => $baseDir . '/lib/public/Migration/Attributes/CreateTable.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\\DropTable' => $baseDir . '/lib/public/Migration/Attributes/DropTable.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\\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',
'OCP\\Migration\\BigIntMigration' => $baseDir . '/lib/public/Migration/BigIntMigration.php',
'OCP\\Migration\\IMigrationStep' => $baseDir . '/lib/public/Migration/IMigrationStep.php',
'OCP\\Migration\\IOutput' => $baseDir . '/lib/public/Migration/IOutput.php',
@@ -1071,6 +1083,7 @@ return array(
'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\\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\\Encryption\\ChangeKeyStorageRoot' => $baseDir . '/core/Command/Encryption/ChangeKeyStorageRoot.php',
'OC\\Core\\Command\\Encryption\\DecryptAll' => $baseDir . '/core/Command/Encryption/DecryptAll.php',
@@ -1565,6 +1578,8 @@ return array(
'OC\\MemoryInfo' => $baseDir . '/lib/private/MemoryInfo.php',
'OC\\Migration\\BackgroundRepair' => $baseDir . '/lib/private/Migration/BackgroundRepair.php',
'OC\\Migration\\ConsoleOutput' => $baseDir . '/lib/private/Migration/ConsoleOutput.php',
+ 'OC\\Migration\\Exceptions\\AttributeException' => $baseDir . '/lib/private/Migration/Exceptions/AttributeException.php',
+ 'OC\\Migration\\MetadataManager' => $baseDir . '/lib/private/Migration/MetadataManager.php',
'OC\\Migration\\NullOutput' => $baseDir . '/lib/private/Migration/NullOutput.php',
'OC\\Migration\\SimpleOutput' => $baseDir . '/lib/private/Migration/SimpleOutput.php',
'OC\\NaturalSort' => $baseDir . '/lib/private/NaturalSort.php',
@@ -1844,6 +1859,8 @@ return array(
'OC\\Updater\\Changes' => $baseDir . '/lib/private/Updater/Changes.php',
'OC\\Updater\\ChangesCheck' => $baseDir . '/lib/private/Updater/ChangesCheck.php',
'OC\\Updater\\ChangesMapper' => $baseDir . '/lib/private/Updater/ChangesMapper.php',
+ 'OC\\Updater\\Exceptions\\ReleaseMetadataException' => $baseDir . '/lib/private/Updater/Exceptions/ReleaseMetadataException.php',
+ 'OC\\Updater\\ReleaseMetadata' => $baseDir . '/lib/private/Updater/ReleaseMetadata.php',
'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php',
'OC\\UserStatus\\ISettableProvider' => $baseDir . '/lib/private/UserStatus/ISettableProvider.php',
'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index db8baeb6341..16fb699964c 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -588,6 +588,18 @@ 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',
+ '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\\CreateTable' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/CreateTable.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\\DropTable' => __DIR__ . '/../../..' . '/lib/public/Migration/Attributes/DropTable.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\\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',
'OCP\\Migration\\BigIntMigration' => __DIR__ . '/../../..' . '/lib/public/Migration/BigIntMigration.php',
'OCP\\Migration\\IMigrationStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IMigrationStep.php',
'OCP\\Migration\\IOutput' => __DIR__ . '/../../..' . '/lib/public/Migration/IOutput.php',
@@ -1104,6 +1116,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'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\\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\\Encryption\\ChangeKeyStorageRoot' => __DIR__ . '/../../..' . '/core/Command/Encryption/ChangeKeyStorageRoot.php',
'OC\\Core\\Command\\Encryption\\DecryptAll' => __DIR__ . '/../../..' . '/core/Command/Encryption/DecryptAll.php',
@@ -1598,6 +1611,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\MemoryInfo' => __DIR__ . '/../../..' . '/lib/private/MemoryInfo.php',
'OC\\Migration\\BackgroundRepair' => __DIR__ . '/../../..' . '/lib/private/Migration/BackgroundRepair.php',
'OC\\Migration\\ConsoleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/ConsoleOutput.php',
+ 'OC\\Migration\\Exceptions\\AttributeException' => __DIR__ . '/../../..' . '/lib/private/Migration/Exceptions/AttributeException.php',
+ 'OC\\Migration\\MetadataManager' => __DIR__ . '/../../..' . '/lib/private/Migration/MetadataManager.php',
'OC\\Migration\\NullOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/NullOutput.php',
'OC\\Migration\\SimpleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/SimpleOutput.php',
'OC\\NaturalSort' => __DIR__ . '/../../..' . '/lib/private/NaturalSort.php',
@@ -1877,6 +1892,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Updater\\Changes' => __DIR__ . '/../../..' . '/lib/private/Updater/Changes.php',
'OC\\Updater\\ChangesCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesCheck.php',
'OC\\Updater\\ChangesMapper' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesMapper.php',
+ 'OC\\Updater\\Exceptions\\ReleaseMetadataException' => __DIR__ . '/../../..' . '/lib/private/Updater/Exceptions/ReleaseMetadataException.php',
+ 'OC\\Updater\\ReleaseMetadata' => __DIR__ . '/../../..' . '/lib/private/Updater/ReleaseMetadata.php',
'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php',
'OC\\UserStatus\\ISettableProvider' => __DIR__ . '/../../..' . '/lib/private/UserStatus/ISettableProvider.php',
'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php',
diff --git a/lib/private/Migration/Exceptions/AttributeException.php b/lib/private/Migration/Exceptions/AttributeException.php
new file mode 100644
index 00000000000..3daf99032ad
--- /dev/null
+++ b/lib/private/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 OC\Migration\Exceptions;
+
+use Exception;
+
+/**
+ * @since 30.0.0
+ */
+class AttributeException extends Exception {
+}
diff --git a/lib/private/Migration/MetadataManager.php b/lib/private/Migration/MetadataManager.php
new file mode 100644
index 00000000000..cf569755ecd
--- /dev/null
+++ b/lib/private/Migration/MetadataManager.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Migration;
+
+use OC\DB\Connection;
+use OC\DB\MigrationService;
+use OC\Migration\Exceptions\AttributeException;
+use OCP\App\IAppManager;
+use OCP\Migration\Attributes\GenericMigrationAttribute;
+use OCP\Migration\Attributes\MigrationAttribute;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Helps managing DB Migrations' Metadata
+ *
+ * @since 30.0.0
+ */
+class MetadataManager {
+ public function __construct(
+ private IAppManager $appManager,
+ private Connection $connection,
+ private LoggerInterface $logger,
+ ) {
+ }
+
+ /**
+ * convert direct data from release metadata into a list of Migrations' Attribute
+ *
+ * @param array<array-key, array<array-key, array>> $metadata
+ * @param bool $filterKnownMigrations ignore metadata already done in local instance
+ *
+ * @return array{apps: array<array-key, array<string, MigrationAttribute[]>>, core: array<string, MigrationAttribute[]>}
+ * @since 30.0.0
+ */
+ public function getMigrationsAttributesFromReleaseMetadata(
+ array $metadata,
+ bool $filterKnownMigrations = false
+ ): array {
+ $appsAttributes = [];
+ foreach (array_keys($metadata['apps']) as $appId) {
+ if ($filterKnownMigrations && !$this->appManager->isInstalled($appId)) {
+ continue; // if not interested and app is not installed
+ }
+
+ $done = ($filterKnownMigrations) ? $this->getKnownMigrations($appId) : [];
+ $appsAttributes[$appId] = $this->parseMigrations($metadata['apps'][$appId] ?? [], $done);
+ }
+
+ $done = ($filterKnownMigrations) ? $this->getKnownMigrations('core') : [];
+ return [
+ 'core' => $this->parseMigrations($metadata['core'] ?? [], $done),
+ 'apps' => $appsAttributes
+ ];
+ }
+
+ /**
+ * convert raw data to a list of MigrationAttribute
+ *
+ * @param array $migrations
+ * @param array $ignoreMigrations
+ *
+ * @return array<string, MigrationAttribute[]>
+ */
+ private function parseMigrations(array $migrations, array $ignoreMigrations = []): array {
+ $parsed = [];
+ foreach (array_keys($migrations) as $entry) {
+ if (in_array($entry, $ignoreMigrations)) {
+ continue;
+ }
+
+ $parsed[$entry] = [];
+ foreach ($migrations[$entry] 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;
+ }
+
+ /**
+ * returns migrations already done
+ *
+ * @param string $appId
+ *
+ * @return array
+ * @throws \Exception
+ */
+ private function getKnownMigrations(string $appId): array {
+ $ms = new MigrationService($appId, $this->connection);
+ return $ms->getMigratedVersions();
+ }
+
+ /**
+ * generate (deserialize) a MigrationAttribute from a serialized version
+ *
+ * @param array $item
+ *
+ * @return MigrationAttribute
+ * @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($item['table'] ?? '');
+ return $attribute->import($item);
+ } catch (\Error) {
+ throw new AttributeException('cannot import Attribute');
+ }
+ }
+}
diff --git a/lib/private/Updater/Exceptions/ReleaseMetadataException.php b/lib/private/Updater/Exceptions/ReleaseMetadataException.php
new file mode 100644
index 00000000000..bc82e4e03df
--- /dev/null
+++ b/lib/private/Updater/Exceptions/ReleaseMetadataException.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 OC\Updater\Exceptions;
+
+use Exception;
+
+/**
+ * @since 30.0.0
+ */
+class ReleaseMetadataException extends Exception {
+}
diff --git a/lib/private/Updater/ReleaseMetadata.php b/lib/private/Updater/ReleaseMetadata.php
new file mode 100644
index 00000000000..4596d1d3fb7
--- /dev/null
+++ b/lib/private/Updater/ReleaseMetadata.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\Updater;
+
+use Exception;
+use JsonException;
+use OC\Updater\Exceptions\ReleaseMetadataException;
+use OCP\Http\Client\IClientService;
+
+/** retrieve releases metadata from official servers
+ *
+ * @since 30.0.0
+ */
+class ReleaseMetadata {
+ public function __construct(
+ private IClientService $clientService,
+ ) {
+ }
+
+ /**
+ * returns metadata based on release version
+ *
+ * - version is a stable release, metadata is downloaded from official releases folder
+ * - version is not a table release, metadata is downloaded from official prereleases folder
+ * - version is a major version (30, 31, 32, ...), latest metadata are downloaded
+ *
+ * @param string $version
+ *
+ * @return array
+ * @throws ReleaseMetadataException
+ * @since 30.0.0
+ */
+ public function getMetadata(string $version): array {
+ if (!str_contains($version, '.')) {
+ $url = 'https://download.nextcloud.com/server/releases/latest-' . $version . '.metadata';
+ } else {
+ [,,$minor] = explode('.', $version);
+ if (ctype_digit($minor)) {
+ $url = 'https://download.nextcloud.com/server/releases/nextcloud-' . $version . '.metadata';
+ } else {
+ $url = 'https://download.nextcloud.com/server/prereleases/nextcloud-' . $version . '.metadata';
+ }
+ }
+ return $this->downloadMetadata($url);
+ }
+
+ /**
+ * download Metadata from a link
+ *
+ * @param string $url
+ *
+ * @return array
+ * @throws ReleaseMetadataException
+ * @since 30.0.0
+ */
+ public function downloadMetadata(string $url): array {
+ $client = $this->clientService->newClient();
+ try {
+ $response = $client->get($url, [
+ 'timeout' => 10,
+ 'connect_timeout' => 10
+ ]);
+ } catch (Exception $e) {
+ throw new ReleaseMetadataException('could not reach metadata at ' . $url, previous: $e);
+ }
+
+ try {
+ return json_decode($response->getBody(), true, flags: JSON_THROW_ON_ERROR);
+ } catch (JsonException) {
+ throw new ReleaseMetadataException('remote document is not valid');
+ }
+ }
+}
diff --git a/lib/public/Migration/Attributes/AddColumn.php b/lib/public/Migration/Attributes/AddColumn.php
new file mode 100644
index 00000000000..3ecae8fde0d
--- /dev/null
+++ b/lib/public/Migration/Attributes/AddColumn.php
@@ -0,0 +1,29 @@
+<?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 on new column creation
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class AddColumn extends ColumnMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return empty($this->getName()) ?
+ 'Addition of a new column to table \'' . $this->getTable() . '\''
+ : 'Addition of column \'' . $this->getName() . '\' to table \'' . $this->getTable() . '\'';
+ }
+}
diff --git a/lib/public/Migration/Attributes/AddIndex.php b/lib/public/Migration/Attributes/AddIndex.php
new file mode 100644
index 00000000000..a7da4173b29
--- /dev/null
+++ b/lib/public/Migration/Attributes/AddIndex.php
@@ -0,0 +1,27 @@
+<?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 on index creation
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class AddIndex extends IndexMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return 'Addition of a new index to table \'' . $this->getTable() . '\'';
+ }
+}
diff --git a/lib/public/Migration/Attributes/ColumnMigrationAttribute.php b/lib/public/Migration/Attributes/ColumnMigrationAttribute.php
new file mode 100644
index 00000000000..8784f5bf2cf
--- /dev/null
+++ b/lib/public/Migration/Attributes/ColumnMigrationAttribute.php
@@ -0,0 +1,78 @@
+<?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;
+
+/**
+ * generic class related to migration attribute about column changes
+ *
+ * @since 30.0.0
+ */
+class ColumnMigrationAttribute extends MigrationAttribute implements JsonSerializable {
+ /**
+ * @param string $table name of the database table
+ * @param string $name name of the column
+ * @param string $description description of the migration
+ * @param array $notes notes about the migration/column
+ * @since 30.0.0
+ */
+ public function __construct(
+ string $table,
+ private string $name = '',
+ string $description = '',
+ array $notes = [],
+ ) {
+ parent::__construct($table, $description, $notes);
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function setName(string $name): self {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function getName(): string {
+ return $this->name;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function import(array $data): self {
+ parent::import($data);
+ $this->setName($data['name'] ?? '');
+ return $this;
+ }
+
+ /**
+ * @return array
+ * @since 30.0.0
+ */
+ public function jsonSerialize(): array {
+ return array_merge(
+ parent::jsonSerialize(),
+ [
+ 'name' => $this->getName(),
+ ]
+ );
+ }
+}
diff --git a/lib/public/Migration/Attributes/CreateTable.php b/lib/public/Migration/Attributes/CreateTable.php
new file mode 100644
index 00000000000..df0418fa4bc
--- /dev/null
+++ b/lib/public/Migration/Attributes/CreateTable.php
@@ -0,0 +1,29 @@
+<?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 on table creation
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class CreateTable extends TableMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ $definition = 'Creation of new table \'' . $this->getTable() . '\'';
+ $definition .= empty($this->getColumns()) ? '' : ' with columns ' . implode(', ', $this->getColumns());
+ return $definition;
+ }
+}
diff --git a/lib/public/Migration/Attributes/DropColumn.php b/lib/public/Migration/Attributes/DropColumn.php
new file mode 100644
index 00000000000..1de0ba58489
--- /dev/null
+++ b/lib/public/Migration/Attributes/DropColumn.php
@@ -0,0 +1,29 @@
+<?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 on column drop
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class DropColumn extends ColumnMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return empty($this->getName()) ?
+ 'Deletion of a column from table \'' . $this->getTable() . '\''
+ : 'Deletion of column \'' . $this->getName() . '\' from table \'' . $this->getTable() . '\'';
+ }
+}
diff --git a/lib/public/Migration/Attributes/DropIndex.php b/lib/public/Migration/Attributes/DropIndex.php
new file mode 100644
index 00000000000..2702cbed9a7
--- /dev/null
+++ b/lib/public/Migration/Attributes/DropIndex.php
@@ -0,0 +1,27 @@
+<?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 on index drop
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class DropIndex extends IndexMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return 'Deletion of an index from table \'' . $this->getTable() . '\'';
+ }
+}
diff --git a/lib/public/Migration/Attributes/DropTable.php b/lib/public/Migration/Attributes/DropTable.php
new file mode 100644
index 00000000000..e90e4804a3c
--- /dev/null
+++ b/lib/public/Migration/Attributes/DropTable.php
@@ -0,0 +1,27 @@
+<?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 on table drop
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class DropTable extends TableMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return 'Deletion of 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..62d3bbf84d1
--- /dev/null
+++ b/lib/public/Migration/Attributes/GenericMigrationAttribute.php
@@ -0,0 +1,49 @@
+<?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;
+
+/**
+ * generic entry, used to replace migration attribute not yet known in current version
+ * but used in a future release
+ *
+ * @since 30.0.0
+ */
+class GenericMigrationAttribute extends MigrationAttribute implements JsonSerializable {
+ /**
+ * @param array $details
+ * @since 30.0.0
+ */
+ public function __construct(
+ private array $details = []
+ ) {
+ parent::__construct(
+ $details['table'] ?? '',
+ $details['description'] ?? '',
+ $details['notes'] ?? []
+ );
+ }
+
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return json_encode($this->jsonSerialize(), JSON_UNESCAPED_SLASHES);
+ }
+
+ /**
+ * @return array
+ * @since 30.0.0
+ */
+ 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..14f800466ea
--- /dev/null
+++ b/lib/public/Migration/Attributes/IndexMigrationAttribute.php
@@ -0,0 +1,32 @@
+<?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;
+
+/**
+ * generic class related to migration attribute about index changes
+ *
+ * @since 30.0.0
+ */
+class IndexMigrationAttribute extends MigrationAttribute implements JsonSerializable {
+ /**
+ * @param string $table name of the database table
+ * @param string $description description of the migration
+ * @param array $notes notes abour the migration/index
+ * @since 30.0.0
+ */
+ public function __construct(
+ string $table,
+ string $description = '',
+ array $notes = [],
+ ) {
+ parent::__construct($table, $description, $notes);
+ }
+}
diff --git a/lib/public/Migration/Attributes/MigrationAttribute.php b/lib/public/Migration/Attributes/MigrationAttribute.php
new file mode 100644
index 00000000000..5b4550c4db5
--- /dev/null
+++ b/lib/public/Migration/Attributes/MigrationAttribute.php
@@ -0,0 +1,118 @@
+<?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;
+
+/**
+ * @since 30.0.0
+ */
+class MigrationAttribute implements JsonSerializable {
+ /**
+ * @param string $table name of the database table
+ * @param string $description description of the migration
+ * @param array $notes notes about the migration
+ * @since 30.0.0
+ */
+ public function __construct(
+ private string $table,
+ private string $description = '',
+ private array $notes = [],
+ ) {
+ }
+
+ /**
+ * @param string $table
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function setTable(string $table): self {
+ $this->table = $table;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function getTable(): string {
+ return $this->table;
+ }
+
+ /**
+ * @param string $description
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function setDescription(string $description): self {
+ $this->description = $description;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function getDescription(): string {
+ return $this->description;
+ }
+
+ /**
+ * @param array $notes
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function setNotes(array $notes): self {
+ $this->notes = $notes;
+ return $this;
+ }
+
+ /**
+ * @return array
+ * @since 30.0.0
+ */
+ public function getNotes(): array {
+ return $this->notes;
+ }
+
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return json_encode($this->jsonSerialize(), JSON_UNESCAPED_SLASHES);
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return self
+ * @since 30.0.0
+ */
+ public function import(array $data): self {
+ return $this->setDescription($data['description'] ?? '')
+ ->setNotes($data['notes'] ?? []);
+ }
+
+ /**
+ * @return array
+ * @since 30.0.0
+ */
+ 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..d90d0db4ec4
--- /dev/null
+++ b/lib/public/Migration/Attributes/ModifyColumn.php
@@ -0,0 +1,29 @@
+<?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 on column modification
+ *
+ * @since 30.0.0
+ */
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+class ModifyColumn extends ColumnMigrationAttribute {
+ /**
+ * @return string
+ * @since 30.0.0
+ */
+ public function definition(): string {
+ return empty($this->getName()) ?
+ 'Modification of a column from table \'' . $this->getTable() . '\''
+ : 'Modification of column \'' . $this->getName() . '\' from table \'' . $this->getTable() . '\'';
+ }
+}
diff --git a/lib/public/Migration/Attributes/TableMigrationAttribute.php b/lib/public/Migration/Attributes/TableMigrationAttribute.php
new file mode 100644
index 00000000000..0776e50387e
--- /dev/null
+++ b/lib/public/Migration/Attributes/TableMigrationAttribute.php
@@ -0,0 +1,78 @@
+<?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;
+
+/**
+ * generic class related to migration attribute about table changes
+ *
+ * @since 30.0.0
+ */
+class TableMigrationAttribute extends MigrationAttribute implements JsonSerializable {
+ /**
+ * @param string $table name of the database table
+ * @param array $columns list of columns
+ * @param string $description description of the migration
+ * @param array $notes notes about the migration/table
+ * @since 30.0.0
+ */
+ public function __construct(
+ string $table,
+ private array $columns = [],
+ string $description = '',
+ array $notes = [],
+ ) {
+ parent::__construct($table, $description, $notes);
+ }
+
+ /**
+ * @param array $columns
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function setColumns(array $columns): self {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * @return array
+ * @since 30.0.0
+ */
+ public function getColumns(): array {
+ return $this->columns;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return $this
+ * @since 30.0.0
+ */
+ public function import(array $data): self {
+ parent::import($data);
+ $this->setColumns($data['columns'] ?? []);
+ return $this;
+ }
+
+ /**
+ * @return array
+ * @since 30.0.0
+ */
+ public function jsonSerialize(): array {
+ return array_merge(
+ parent::jsonSerialize(),
+ [
+ 'columns' => $this->getColumns(),
+ ]
+ );
+ }
+}
diff --git a/tests/lib/Updater/ReleaseMetadataTest.php b/tests/lib/Updater/ReleaseMetadataTest.php
new file mode 100644
index 00000000000..25fe3949152
--- /dev/null
+++ b/tests/lib/Updater/ReleaseMetadataTest.php
@@ -0,0 +1,209 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace Test\Updater;
+
+use OC\Updater\ReleaseMetadata;
+use OCP\Http\Client\IClient;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use PHPUnit\Framework\MockObject\MockObject;
+
+class ReleaseMetadataTest extends \Test\TestCase {
+ private IClientService| MockObject $clientService;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->clientService = $this->getMockBuilder(IClientService::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function testDownloadMetadata() {
+ $client = $this->createMock(IClient::class);
+ $response = $this->createMock(IResponse::class);
+ $this->clientService->expects($this->once())
+ ->method('newClient')
+ ->with()
+ ->willReturn($client);
+ $client->expects($this->once())
+ ->method('get')
+ ->willReturn($response);
+ $response->expects($this->once())
+ ->method('getBody')
+ ->with()
+ ->willReturn($this->resultRequest());
+
+
+ $releaseMetadata = new ReleaseMetadata($this->clientService);
+ $this->assertSame($this->resultRequestArray(), $releaseMetadata->downloadMetadata('ouila'));
+ }
+
+ /**
+ * @dataProvider getMetadataUrlProvider
+ *
+ * @param string $version
+ * @param string $url
+ */
+ public function testGetMetadata(string $version, string $url) {
+ $client = $this->createMock(IClient::class);
+ $response = $this->createMock(IResponse::class);
+ $this->clientService->expects($this->once())
+ ->method('newClient')
+ ->with()
+ ->willReturn($client);
+ $client->expects($this->once())
+ ->method('get')
+ ->with($url)
+ ->willReturn($response);
+
+ $response->expects($this->once())
+ ->method('getBody')
+ ->with()
+ ->willReturn('{}');
+
+ $releaseMetadata = new ReleaseMetadata($this->clientService);
+ $releaseMetadata->getMetadata($version);
+ }
+
+ /**
+ * @return array
+ */
+ public function getMetadataUrlProvider(): array {
+ return [
+ [
+ '30.0.0',
+ 'https://download.nextcloud.com/server/releases/nextcloud-30.0.0.metadata'
+ ],
+ [
+ '30.0.0-beta1',
+ 'https://download.nextcloud.com/server/prereleases/nextcloud-30.0.0-beta1.metadata'
+ ],
+ [
+ '30',
+ 'https://download.nextcloud.com/server/releases/latest-30.metadata'
+ ]
+ ];
+ }
+
+ private function resultRequest(): string {
+ return json_encode($this->resultRequestArray());
+ }
+
+ private function resultRequestArray(): array {
+ return [
+ 'migrations' => [
+ 'core' => [],
+ 'apps' => [
+ 'testing' => [
+ '30000Date20240102030405' => [
+ 'class' => 'OCP\\Migration\\Attributes\\DropTable',
+ 'table' => 'old_table',
+ 'description' => '',
+ 'notes' => [],
+ 'columns' => []
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\CreateTable',
+ 'table' => 'new_table',
+ 'description' => 'Table is used to store things, but also to get more things',
+ 'notes' => [
+ 'this is a notice',
+ 'and another one, if really needed'
+ ],
+ 'columns' => []
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\AddColumn',
+ 'table' => 'my_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => '',
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\AddColumn',
+ 'table' => 'my_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => 'another_field',
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\AddColumn',
+ 'table' => 'other_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => 'last_one',
+ 'type' => 'date'
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\AddIndex',
+ 'table' => 'my_table',
+ 'description' => '',
+ 'notes' => [],
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\AddIndex',
+ 'table' => 'my_table',
+ 'description' => '',
+ 'notes' => [],
+ 'type' => 'primary'
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\DropColumn',
+ 'table' => 'other_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => '',
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\DropColumn',
+ 'table' => 'other_table',
+ 'description' => 'field is not used anymore and replaced by \'last_one\'',
+ 'notes' => [],
+ 'name' => 'old_column',
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\DropIndex',
+ 'table' => 'other_table',
+ 'description' => '',
+ 'notes' => [],
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\ModifyColumn',
+ 'table' => 'other_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => '',
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\ModifyColumn',
+ 'table' => 'other_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => 'this_field',
+ 'type' => ''
+ ],
+ [
+ 'class' => 'OCP\\Migration\\Attributes\\ModifyColumn',
+ 'table' => 'other_table',
+ 'description' => '',
+ 'notes' => [],
+ 'name' => 'this_field',
+ 'type' => 'bigint'
+ ]
+ ]
+ ]
+ ]
+ ];
+ }
+}