Page MenuHomePhabricator

D10595.diff
No OneTemporary

D10595.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2310,6 +2310,7 @@
'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php',
'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php',
'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php',
+ 'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php',
'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php',
'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php',
'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php',
@@ -5313,6 +5314,7 @@
'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldPHIDs',
'PhabricatorStandardPageView' => 'PhabricatorBarePageView',
'PhabricatorStatusController' => 'PhabricatorController',
+ 'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow',
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php
@@ -0,0 +1,227 @@
+<?php
+
+final class PhabricatorStorageManagementAdjustWorkflow
+ extends PhabricatorStorageManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->setName('adjust')
+ ->setExamples('**adjust** [__options__]')
+ ->setSynopsis(
+ pht(
+ 'Make schemata adjustments to correct issues with characters sets, '.
+ 'collations, and keys.'));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $this->requireAllPatchesApplied();
+ $this->adjustSchemata();
+ return 0;
+ }
+
+ private function requireAllPatchesApplied() {
+ $api = $this->getAPI();
+ $applied = $api->getAppliedPatches();
+
+ if ($applied === null) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You have not initialized the database yet. You must initialize '.
+ 'the database before you can adjust schemata. Run `storage upgrade` '.
+ 'to initialize the database.'));
+ }
+
+ $applied = array_fuse($applied);
+
+ $patches = $this->getPatches();
+ $patches = mpull($patches, null, 'getFullKey');
+ $missing = array_diff_key($patches, $applied);
+
+ if ($missing) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You have not applied all available storage patches yet. You must '.
+ 'apply all available patches before you can adjust schemata. '.
+ 'Run `storage status` to show patch status, and `storage upgrade` '.
+ 'to apply missing patches.'));
+ }
+ }
+
+ private function loadSchemata() {
+ $query = id(new PhabricatorConfigSchemaQuery())
+ ->setAPI($this->getAPI());
+
+ $actual = $query->loadActualSchema();
+ $expect = $query->loadExpectedSchema();
+ $comp = $query->buildComparisonSchema($expect, $actual);
+
+ return array($comp, $expect, $actual);
+ }
+
+ private function adjustSchemata() {
+ $console = PhutilConsole::getConsole();
+
+ $console->writeOut(
+ "%s\n",
+ pht('Verifying database schemata...'));
+
+ $adjustments = $this->findAdjustments();
+
+ if (!$adjustments) {
+ $console->writeOut(
+ "%s\n",
+ pht('Found no issues with schemata.'));
+ return;
+ }
+
+ $table = id(new PhutilConsoleTable())
+ ->addColumn('database', array('title' => pht('Database')))
+ ->addColumn('table', array('title' => pht('Table')))
+ ->addColumn('name', array('title' => pht('Name')))
+ ->addColumn('info', array('title' => pht('Issues')));
+
+ foreach ($adjustments as $adjust) {
+ $info = array();
+ foreach ($adjust['issues'] as $issue) {
+ $info[] = PhabricatorConfigStorageSchema::getIssueName($issue);
+ }
+
+ $table->addRow(array(
+ 'database' => $adjust['database'],
+ 'table' => idx($adjust, 'table'),
+ 'name' => idx($adjust, 'name'),
+ 'info' => implode(', ', $info),
+ ));
+ }
+
+ $console->writeOut("\n\n");
+
+ $table->draw();
+
+ $console->writeOut(
+ "\n%s\n",
+ pht(
+ "Found %s issues(s) with schemata, detailed above.\n\n".
+ "You can review issues in more detail from the web interface, ".
+ "in Config > Database Status.\n\n".
+ "MySQL needs to copy table data to make some adjustments, so these ".
+ "migrations may take some time.".
+
+ // TODO: Remove warning once this stabilizes.
+ "\n\n".
+ "WARNING: This workflow is new and unstable. If you continue, you ".
+ "may unrecoverably destory data. Make sure you have a backup before ".
+ "you proceed.",
+
+ new PhutilNumber(count($adjustments))));
+
+ $prompt = pht('Fix these schema issues?');
+ if (!phutil_console_confirm($prompt, $default_no = true)) {
+ return;
+ }
+
+ $console->writeOut(
+ "%s\n",
+ pht('Fixing schema issues...'));
+
+ $api = $this->getAPI();
+ $conn = $api->getConn(null);
+
+ $bar = id(new PhutilConsoleProgressBar())
+ ->setTotal(count($adjustments));
+ foreach ($adjustments as $adjust) {
+ switch ($adjust['kind']) {
+ case 'database':
+ queryfx(
+ $conn,
+ 'ALTER DATABASE %T CHARACTER SET = %s COLLATE = %s',
+ $adjust['database'],
+ $adjust['charset'],
+ $adjust['collation']);
+ break;
+ case 'table':
+ queryfx(
+ $conn,
+ 'ALTER TABLE %T.%T COLLATE = %s',
+ $adjust['database'],
+ $adjust['table'],
+ $adjust['collation']);
+ break;
+ default:
+ throw new Exception(
+ pht('Unknown schema adjustment kind "%s"!', $adjust['kind']));
+ }
+
+ $bar->update(1);
+ }
+ $bar->done();
+
+ $console->writeOut(
+ "%s\n",
+ pht('Completed fixing all schema issues.'));
+ }
+
+ private function findAdjustments() {
+ list($comp, $expect, $actual) = $this->loadSchemata();
+
+ $issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
+ $issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
+
+ $adjustments = array();
+ foreach ($comp->getDatabases() as $database_name => $database) {
+ $expect_database = $expect->getDatabase($database_name);
+ $actual_database = $actual->getDatabase($database_name);
+
+ if (!$expect_database || !$actual_database) {
+ // If there's a real issue here, skip this stuff.
+ continue;
+ }
+
+ $issues = array();
+ if ($database->hasIssue($issue_charset)) {
+ $issues[] = $issue_charset;
+ }
+ if ($database->hasIssue($issue_collation)) {
+ $issues[] = $issue_collation;
+ }
+
+ if ($issues) {
+ $adjustments[] = array(
+ 'kind' => 'database',
+ 'database' => $database_name,
+ 'issues' => $issues,
+ 'charset' => $expect_database->getCharacterSet(),
+ 'collation' => $expect_database->getCollation(),
+ );
+ }
+
+ foreach ($database->getTables() as $table_name => $table) {
+ $expect_table = $expect_database->getTable($table_name);
+ $actual_table = $actual_database->getTable($table_name);
+
+ if (!$expect_table || !$actual_table) {
+ continue;
+ }
+
+ $issues = array();
+ if ($table->hasIssue($issue_collation)) {
+ $issues[] = $issue_collation;
+ }
+
+ if ($issues) {
+ $adjustments[] = array(
+ 'kind' => 'table',
+ 'database' => $database_name,
+ 'table' => $table_name,
+ 'issues' => $issues,
+ 'collation' => $expect_table->getCollation(),
+ );
+ }
+ }
+ }
+
+ return $adjustments;
+ }
+
+
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 1, 2:48 AM (2 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6732411
Default Alt Text
D10595.diff (8 KB)

Event Timeline