@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add `bin/storage adjust`, for adjusting schemata

Summary:
Ref T1191. Adds a new workflow which can apply schema adjustments.

For now, it only performs database and table collation/charset adjustments. I believe these are extremely safe/minor, because they only affect the default values for newly created columns.

Test Plan:
- Ran migration on various database states, database/table changes went through cleanly.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1191

Differential Revision: https://secure.phabricator.com/D10595

+229
+2
src/__phutil_library_map__.php
··· 2310 2310 'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php', 2311 2311 'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php', 2312 2312 'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php', 2313 + 'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php', 2313 2314 'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php', 2314 2315 'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php', 2315 2316 'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php', ··· 5313 5314 'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldPHIDs', 5314 5315 'PhabricatorStandardPageView' => 'PhabricatorBarePageView', 5315 5316 'PhabricatorStatusController' => 'PhabricatorController', 5317 + 'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow', 5316 5318 'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow', 5317 5319 'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow', 5318 5320 'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow',
+227
src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php
··· 1 + <?php 2 + 3 + final class PhabricatorStorageManagementAdjustWorkflow 4 + extends PhabricatorStorageManagementWorkflow { 5 + 6 + public function didConstruct() { 7 + $this 8 + ->setName('adjust') 9 + ->setExamples('**adjust** [__options__]') 10 + ->setSynopsis( 11 + pht( 12 + 'Make schemata adjustments to correct issues with characters sets, '. 13 + 'collations, and keys.')); 14 + } 15 + 16 + public function execute(PhutilArgumentParser $args) { 17 + $this->requireAllPatchesApplied(); 18 + $this->adjustSchemata(); 19 + return 0; 20 + } 21 + 22 + private function requireAllPatchesApplied() { 23 + $api = $this->getAPI(); 24 + $applied = $api->getAppliedPatches(); 25 + 26 + if ($applied === null) { 27 + throw new PhutilArgumentUsageException( 28 + pht( 29 + 'You have not initialized the database yet. You must initialize '. 30 + 'the database before you can adjust schemata. Run `storage upgrade` '. 31 + 'to initialize the database.')); 32 + } 33 + 34 + $applied = array_fuse($applied); 35 + 36 + $patches = $this->getPatches(); 37 + $patches = mpull($patches, null, 'getFullKey'); 38 + $missing = array_diff_key($patches, $applied); 39 + 40 + if ($missing) { 41 + throw new PhutilArgumentUsageException( 42 + pht( 43 + 'You have not applied all available storage patches yet. You must '. 44 + 'apply all available patches before you can adjust schemata. '. 45 + 'Run `storage status` to show patch status, and `storage upgrade` '. 46 + 'to apply missing patches.')); 47 + } 48 + } 49 + 50 + private function loadSchemata() { 51 + $query = id(new PhabricatorConfigSchemaQuery()) 52 + ->setAPI($this->getAPI()); 53 + 54 + $actual = $query->loadActualSchema(); 55 + $expect = $query->loadExpectedSchema(); 56 + $comp = $query->buildComparisonSchema($expect, $actual); 57 + 58 + return array($comp, $expect, $actual); 59 + } 60 + 61 + private function adjustSchemata() { 62 + $console = PhutilConsole::getConsole(); 63 + 64 + $console->writeOut( 65 + "%s\n", 66 + pht('Verifying database schemata...')); 67 + 68 + $adjustments = $this->findAdjustments(); 69 + 70 + if (!$adjustments) { 71 + $console->writeOut( 72 + "%s\n", 73 + pht('Found no issues with schemata.')); 74 + return; 75 + } 76 + 77 + $table = id(new PhutilConsoleTable()) 78 + ->addColumn('database', array('title' => pht('Database'))) 79 + ->addColumn('table', array('title' => pht('Table'))) 80 + ->addColumn('name', array('title' => pht('Name'))) 81 + ->addColumn('info', array('title' => pht('Issues'))); 82 + 83 + foreach ($adjustments as $adjust) { 84 + $info = array(); 85 + foreach ($adjust['issues'] as $issue) { 86 + $info[] = PhabricatorConfigStorageSchema::getIssueName($issue); 87 + } 88 + 89 + $table->addRow(array( 90 + 'database' => $adjust['database'], 91 + 'table' => idx($adjust, 'table'), 92 + 'name' => idx($adjust, 'name'), 93 + 'info' => implode(', ', $info), 94 + )); 95 + } 96 + 97 + $console->writeOut("\n\n"); 98 + 99 + $table->draw(); 100 + 101 + $console->writeOut( 102 + "\n%s\n", 103 + pht( 104 + "Found %s issues(s) with schemata, detailed above.\n\n". 105 + "You can review issues in more detail from the web interface, ". 106 + "in Config > Database Status.\n\n". 107 + "MySQL needs to copy table data to make some adjustments, so these ". 108 + "migrations may take some time.". 109 + 110 + // TODO: Remove warning once this stabilizes. 111 + "\n\n". 112 + "WARNING: This workflow is new and unstable. If you continue, you ". 113 + "may unrecoverably destory data. Make sure you have a backup before ". 114 + "you proceed.", 115 + 116 + new PhutilNumber(count($adjustments)))); 117 + 118 + $prompt = pht('Fix these schema issues?'); 119 + if (!phutil_console_confirm($prompt, $default_no = true)) { 120 + return; 121 + } 122 + 123 + $console->writeOut( 124 + "%s\n", 125 + pht('Fixing schema issues...')); 126 + 127 + $api = $this->getAPI(); 128 + $conn = $api->getConn(null); 129 + 130 + $bar = id(new PhutilConsoleProgressBar()) 131 + ->setTotal(count($adjustments)); 132 + foreach ($adjustments as $adjust) { 133 + switch ($adjust['kind']) { 134 + case 'database': 135 + queryfx( 136 + $conn, 137 + 'ALTER DATABASE %T CHARACTER SET = %s COLLATE = %s', 138 + $adjust['database'], 139 + $adjust['charset'], 140 + $adjust['collation']); 141 + break; 142 + case 'table': 143 + queryfx( 144 + $conn, 145 + 'ALTER TABLE %T.%T COLLATE = %s', 146 + $adjust['database'], 147 + $adjust['table'], 148 + $adjust['collation']); 149 + break; 150 + default: 151 + throw new Exception( 152 + pht('Unknown schema adjustment kind "%s"!', $adjust['kind'])); 153 + } 154 + 155 + $bar->update(1); 156 + } 157 + $bar->done(); 158 + 159 + $console->writeOut( 160 + "%s\n", 161 + pht('Completed fixing all schema issues.')); 162 + } 163 + 164 + private function findAdjustments() { 165 + list($comp, $expect, $actual) = $this->loadSchemata(); 166 + 167 + $issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 168 + $issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 169 + 170 + $adjustments = array(); 171 + foreach ($comp->getDatabases() as $database_name => $database) { 172 + $expect_database = $expect->getDatabase($database_name); 173 + $actual_database = $actual->getDatabase($database_name); 174 + 175 + if (!$expect_database || !$actual_database) { 176 + // If there's a real issue here, skip this stuff. 177 + continue; 178 + } 179 + 180 + $issues = array(); 181 + if ($database->hasIssue($issue_charset)) { 182 + $issues[] = $issue_charset; 183 + } 184 + if ($database->hasIssue($issue_collation)) { 185 + $issues[] = $issue_collation; 186 + } 187 + 188 + if ($issues) { 189 + $adjustments[] = array( 190 + 'kind' => 'database', 191 + 'database' => $database_name, 192 + 'issues' => $issues, 193 + 'charset' => $expect_database->getCharacterSet(), 194 + 'collation' => $expect_database->getCollation(), 195 + ); 196 + } 197 + 198 + foreach ($database->getTables() as $table_name => $table) { 199 + $expect_table = $expect_database->getTable($table_name); 200 + $actual_table = $actual_database->getTable($table_name); 201 + 202 + if (!$expect_table || !$actual_table) { 203 + continue; 204 + } 205 + 206 + $issues = array(); 207 + if ($table->hasIssue($issue_collation)) { 208 + $issues[] = $issue_collation; 209 + } 210 + 211 + if ($issues) { 212 + $adjustments[] = array( 213 + 'kind' => 'table', 214 + 'database' => $database_name, 215 + 'table' => $table_name, 216 + 'issues' => $issues, 217 + 'collation' => $expect_table->getCollation(), 218 + ); 219 + } 220 + } 221 + } 222 + 223 + return $adjustments; 224 + } 225 + 226 + 227 + }