From a9ec99253567ebf73d83d254af5f5cfe9678ed26 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 25 Aug 2026 14:25:22 +0200 Subject: [PATCH] fix(migration): make option_type migration self-sufficient Version050300Date20260716000000 guarded changeSchema() against a missing option_type column but ran an unguarded UPDATE on it in postSchemaChange(), aborting occ upgrade on any instance where the column was absent. Create the column when missing and guard the backfill in both migrations. Fixes #3562 Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger --- .../Version050300Date20250914000000.php | 9 ++++- .../Version050300Date20260716000000.php | 36 ++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/Migration/Version050300Date20250914000000.php b/lib/Migration/Version050300Date20250914000000.php index b875b35d2..575d17b8e 100644 --- a/lib/Migration/Version050300Date20250914000000.php +++ b/lib/Migration/Version050300Date20250914000000.php @@ -34,9 +34,10 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $schema = $schemaClosure(); $table = $schema->getTable('forms_v2_options'); - if (!$table->hascolumn('option_type')) { + if (!$table->hasColumn('option_type')) { $table->addColumn('option_type', Types::STRING, [ 'notnull' => false, + 'length' => 255, 'default' => null, ]); } @@ -50,6 +51,12 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt * @param array $options */ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + if (!$schema->getTable('forms_v2_options')->hasColumn('option_type')) { + return; + } + $qbUpdate = $this->db->getQueryBuilder(); $qbUpdate->update('forms_v2_options') diff --git a/lib/Migration/Version050300Date20260716000000.php b/lib/Migration/Version050300Date20260716000000.php index c5becf402..2f77899b8 100644 --- a/lib/Migration/Version050300Date20260716000000.php +++ b/lib/Migration/Version050300Date20260716000000.php @@ -11,6 +11,7 @@ use Closure; use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; @@ -20,6 +21,11 @@ * rows that were stored without a type. Options created through the API * without an explicit optionType previously kept a null type, which the * frontend does not render. + * + * The column is normally created by Version050300Date20250914000000. That + * migration being recorded in oc_migrations is not a guarantee that its DDL + * was applied, so this step recreates the column when it is missing instead + * of failing the whole upgrade. */ class Version050300Date20260716000000 extends SimpleMigrationStep { @@ -38,17 +44,25 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt /** @var ISchemaWrapper $schema */ $schema = $schemaClosure(); $table = $schema->getTable('forms_v2_options'); - $changed = false; - if ($table->hasColumn('option_type')) { - $column = $table->getColumn('option_type'); - if ($column->getDefault() === null) { - $column->setDefault('choice'); - $changed = true; - } + if (!$table->hasColumn('option_type')) { + $table->addColumn('option_type', Types::STRING, [ + 'notnull' => false, + 'length' => 255, + 'default' => 'choice', + ]); + + return $schema; + } + + $column = $table->getColumn('option_type'); + if ($column->getDefault() === null) { + $column->setDefault('choice'); + + return $schema; } - return $changed ? $schema : null; + return null; } /** @@ -57,6 +71,12 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt * @param array $options */ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + if (!$schema->getTable('forms_v2_options')->hasColumn('option_type')) { + return; + } + $qbUpdate = $this->db->getQueryBuilder(); $qbUpdate->update('forms_v2_options')