Migrations
Migrations change the database schema and operate in three steps:
Pre schema changes
Schema changes
Post schema changes
Apps can have multiple migrations, which allows a way more flexible updating process. For example, you can rename a column while copying all the content with 3 steps packed in 2 migrations.
The Nextcloud updater logic will look for your migration files in the apps lib/Migration folder.
Note
While in theory you can run any code in the pre- and post-steps, we recommend not to use actual php classes. With migrations you can update from any old version to any new version as long as the migration steps are retained. Since they are also used for installation, you should keep them anyway. But this also means when you change a php class which you use in your migration, the code may be executed on different database/file/code standings when being ran in an upgrade situation.
Note
Since Nextcloud stores, which migrations have been executed already you must not “update” migrations. The recommendation is to keep them untouched as long as possible. You should only adjust it to make sure it still executes, but additional changes to the database should be done in a new migration.
1. Migration 1: Schema change
With this step the new column gets created:
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('twofactor_backupcodes');
$table->addColumn('user_id', \OCP\DB\Types::STRING, [
'notnull' => true,
'length' => 64,
]);
return $schema;
}
2. Migration 1: Post schema change
In this step the content gets copied from the old to the new column.
Note
This could also be done as part of the second migration as part of a pre schema change
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
$query = $this->db->getQueryBuilder();
$query->update('twofactor_backupcodes')
->set('user_id', 'uid');
$query->executeStatement();
}
3. Migration 2: Schema change
With this the old column gets removed.
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('twofactor_backupcodes');
$table->dropColumn('uid');
return $schema;
}
Construction of migration classes
All migration classes are constructed via Basic Pattern of Dependency Injection. So if your migration steps need additional dependencies, these can be defined in the constructor of your migration class.
Example: If your migration needs to execute SQL statements, inject a OCP\IDBConnection instance into your migration class like this:
class Version2404Date20220903071748 extends SimpleMigrationStep {
public function __construct(
private IDBConnection $db
) {
}
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
$query = $this->db->getQueryBuilder();
// execute some SQL ...
}
}
Migrations and Metadata
Since 30, details about migrations are available to administrator as metadata can be attached to your migration class by adding specific PHP Attributes:
use OCP\Migration\Attributes\CreateTable;
use OCP\Migration\Attributes\ColumnType;
use OCP\Migration\Attributes\ModifyColumn;
#[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']
)]
#[ModifyColumn(table: 'other_table', name: 'this_field', type: ColumnType::BIGINT)]
class Version30000Date20240729185117 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
[...]
}
}
List of available Migration Attributes:
\OCP\Migration\Attributes\AddColumnif your migration implies the creation of a new column\OCP\Migration\Attributes\AddIndexif your migration adds a new index\OCP\Migration\Attributes\CreateTableif your migration creates a new table\OCP\Migration\Attributes\DropColumnif your migration drops a column\OCP\Migration\Attributes\DropIndexif your migration drops an index\OCP\Migration\Attributes\DropTableif your migration drops a table\OCP\Migration\Attributes\ModifyColumnif your migration modifies a column
Console commands
The following occ commands help you create and manage migrations:
migrations
migrations:execute execute a single migration version manually
migrations:generate generate a new migration file for an app
migrations:migrate execute migrations to a specified or the latest version
migrations:preview preview available DB migrations before an upgrade
migrations:status view the status of migrations for an app
migrations:execute
Execute a single migration version manually. The version argument is the
migration class name without the Version prefix — for example, if your
migration is named Version2404Date20220903071748, the version is
2404Date20220903071748:
sudo -E -u www-data php occ migrations:execute myapp 2404Date20220903071748
Note
Without debug mode enabled, migrations:execute will refuse to run a
version that has already been executed or that would roll back a previous
migration. Enable debug mode (’debug’ => true in config.php) to
override this restriction during development.
migrations:generate
Generate a new migration file for an app. The version argument is the
major version of your app as an integer. Use the major and minor digits of
your app version, mapped to 3 digits (1.0.x => 1000, 2.34.x => 2034),
to leave room for parallel branch migrations:
sudo -E -u www-data php occ migrations:generate myapp 1000
The generated file is placed in apps/myapp/lib/Migration/.
Note
After generating a migration you may need to run composer dump-autoload
before it can be executed.
migrations:migrate
Execute all pending migrations for an app, or migrate to a specific version.
Accepts a version number (YYYYMMDDHHMMSS) or an alias (first,
prev, next, latest):
sudo -E -u www-data php occ migrations:migrate myapp
sudo -E -u www-data php occ migrations:migrate myapp prev
migrations:preview
Preview the DB migrations that would be applied during an upgrade to a given version, without executing them:
sudo -E -u www-data php occ migrations:preview 30.0.0
migrations:status
Show which migrations have been executed and which are pending for an app:
sudo -E -u www-data php occ migrations:status myapp
Adding indices
Adding indices to existing tables can take long time, especially on large tables. Therefore it is recommended to not add the indices in the migration itself, but to indicate the index requirement to the server by adding a listener for the AddMissingIndicesEvent. This way the migration can be executed in a separate step and do not block the upgrade process. For new installations the index should still be added to the migration that creates the table.
class AddMissingIndicesListener implements IEventListener {
public function handle(Event $event): void {
if (!$event instanceof AddMissingIndicesEvent) {
return;
}
$event->addMissingIndex('my_table', 'my_index', ['column_a', 'column_b']);
}
}
Replacing indices
Added in version 29.0.0.
Similar to adding an index to an existing table, it could be necessary to replace one or more indices with a new one. To avoid a gap between dropping the old indices in a migration and adding the new one through AddMissingIndicesEvent, it is possible to do both at once in AddMissingIndicesEvent.
If none of the previous indices are found, e.g. because they were optional and not created yet, the replacement index will be treated as missing index.
Note
Make sure to not use the same index name for the new index as for old indices.
class ReplaceIndicesListener implements IEventListener {
public function handle(Event $event): void {
if (!$event instanceof AddMissingIndicesEvent) {
return;
}
$event->replaceIndex('my_table', ['my_old_index_one', 'my_old_index_two'], 'my_new_index', ['column_a', 'column_b'], false);
}
}