Differential D10601 Diff 25473 src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php
Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php
<?php | <?php | ||||
final class PhabricatorStorageManagementAdjustWorkflow | final class PhabricatorStorageManagementAdjustWorkflow | ||||
extends PhabricatorStorageManagementWorkflow { | extends PhabricatorStorageManagementWorkflow { | ||||
public function didConstruct() { | public function didConstruct() { | ||||
$this | $this | ||||
->setName('adjust') | ->setName('adjust') | ||||
->setExamples('**adjust** [__options__]') | ->setExamples('**adjust** [__options__]') | ||||
->setSynopsis( | ->setSynopsis( | ||||
pht( | pht( | ||||
'Make schemata adjustments to correct issues with characters sets, '. | 'Make schemata adjustments to correct issues with characters sets, '. | ||||
'collations, and keys.')); | 'collations, and keys.')); | ||||
} | } | ||||
public function execute(PhutilArgumentParser $args) { | public function execute(PhutilArgumentParser $args) { | ||||
$force = $args->getArg('force'); | |||||
$this->requireAllPatchesApplied(); | $this->requireAllPatchesApplied(); | ||||
$this->adjustSchemata(); | $this->adjustSchemata($force); | ||||
return 0; | return 0; | ||||
} | } | ||||
private function requireAllPatchesApplied() { | private function requireAllPatchesApplied() { | ||||
$api = $this->getAPI(); | $api = $this->getAPI(); | ||||
$applied = $api->getAppliedPatches(); | $applied = $api->getAppliedPatches(); | ||||
if ($applied === null) { | if ($applied === null) { | ||||
Show All 26 Lines | private function loadSchemata() { | ||||
$actual = $query->loadActualSchema(); | $actual = $query->loadActualSchema(); | ||||
$expect = $query->loadExpectedSchema(); | $expect = $query->loadExpectedSchema(); | ||||
$comp = $query->buildComparisonSchema($expect, $actual); | $comp = $query->buildComparisonSchema($expect, $actual); | ||||
return array($comp, $expect, $actual); | return array($comp, $expect, $actual); | ||||
} | } | ||||
private function adjustSchemata() { | private function adjustSchemata($force) { | ||||
$console = PhutilConsole::getConsole(); | $console = PhutilConsole::getConsole(); | ||||
$console->writeOut( | $console->writeOut( | ||||
"%s\n", | "%s\n", | ||||
pht('Verifying database schemata...')); | pht('Verifying database schemata...')); | ||||
$adjustments = $this->findAdjustments(); | $adjustments = $this->findAdjustments(); | ||||
Show All 23 Lines | foreach ($adjustments as $adjust) { | ||||
'info' => implode(', ', $info), | 'info' => implode(', ', $info), | ||||
)); | )); | ||||
} | } | ||||
$console->writeOut("\n\n"); | $console->writeOut("\n\n"); | ||||
$table->draw(); | $table->draw(); | ||||
if (!$force) { | |||||
$console->writeOut( | $console->writeOut( | ||||
"\n%s\n", | "\n%s\n", | ||||
pht( | pht( | ||||
"Found %s issues(s) with schemata, detailed above.\n\n". | "Found %s issues(s) with schemata, detailed above.\n\n". | ||||
"You can review issues in more detail from the web interface, ". | "You can review issues in more detail from the web interface, ". | ||||
"in Config > Database Status.\n\n". | "in Config > Database Status.\n\n". | ||||
"MySQL needs to copy table data to make some adjustments, so these ". | "MySQL needs to copy table data to make some adjustments, so these ". | ||||
"migrations may take some time.". | "migrations may take some time.". | ||||
// TODO: Remove warning once this stabilizes. | // TODO: Remove warning once this stabilizes. | ||||
"\n\n". | "\n\n". | ||||
"WARNING: This workflow is new and unstable. If you continue, you ". | "WARNING: This workflow is new and unstable. If you continue, you ". | ||||
"may unrecoverably destory data. Make sure you have a backup before ". | "may unrecoverably destory data. Make sure you have a backup before ". | ||||
"you proceed.", | "you proceed.", | ||||
new PhutilNumber(count($adjustments)))); | new PhutilNumber(count($adjustments)))); | ||||
$prompt = pht('Fix these schema issues?'); | $prompt = pht('Fix these schema issues?'); | ||||
if (!phutil_console_confirm($prompt, $default_no = true)) { | if (!phutil_console_confirm($prompt, $default_no = true)) { | ||||
return; | return; | ||||
} | } | ||||
} | |||||
$console->writeOut( | $console->writeOut( | ||||
"%s\n", | "%s\n", | ||||
pht('Fixing schema issues...')); | pht('Fixing schema issues...')); | ||||
$api = $this->getAPI(); | $api = $this->getAPI(); | ||||
$conn = $api->getConn(null); | $conn = $api->getConn(null); | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | for ($phase = 0; $phase < $phases; $phase++) { | ||||
$adjust['name'], | $adjust['name'], | ||||
$adjust['type'], | $adjust['type'], | ||||
implode(' ', $parts), | implode(' ', $parts), | ||||
$adjust['nullable'] ? 'NULL' : 'NOT NULL'); | $adjust['nullable'] ? 'NULL' : 'NOT NULL'); | ||||
break; | break; | ||||
case 'key': | case 'key': | ||||
if (($phase == 0) && $adjust['exists']) { | if (($phase == 0) && $adjust['exists']) { | ||||
if ($adjust['name'] == 'PRIMARY') { | |||||
$key_name = 'PRIMARY KEY'; | |||||
} else { | |||||
$key_name = qsprintf($conn, 'KEY %T', $adjust['name']); | |||||
} | |||||
queryfx( | queryfx( | ||||
$conn, | $conn, | ||||
'ALTER TABLE %T.%T DROP KEY %T', | 'ALTER TABLE %T.%T DROP %Q', | ||||
$adjust['database'], | $adjust['database'], | ||||
$adjust['table'], | $adjust['table'], | ||||
$adjust['name']); | $key_name); | ||||
} | } | ||||
if (($phase == 2) && $adjust['keep']) { | if (($phase == 2) && $adjust['keep']) { | ||||
// Different keys need different creation syntax. Notable | |||||
// special cases are primary keys and fulltext keys. | |||||
if ($adjust['name'] == 'PRIMARY') { | |||||
$key_name = 'PRIMARY KEY'; | |||||
} else if ($adjust['indexType'] == 'FULLTEXT') { | |||||
$key_name = qsprintf($conn, 'FULLTEXT %T', $adjust['name']); | |||||
} else { | |||||
if ($adjust['unique']) { | |||||
$key_name = qsprintf( | |||||
$conn, | |||||
'UNIQUE KEY %T', | |||||
$adjust['name']); | |||||
} else { | |||||
$key_name = qsprintf( | |||||
$conn, | |||||
'/* NONUNIQUE */ KEY %T', | |||||
$adjust['name']); | |||||
} | |||||
} | |||||
epriestley: Various keys need different grammar. | |||||
queryfx( | queryfx( | ||||
$conn, | $conn, | ||||
'ALTER TABLE %T.%T ADD %Q KEY %T (%Q)', | 'ALTER TABLE %T.%T ADD %Q (%Q)', | ||||
$adjust['database'], | $adjust['database'], | ||||
$adjust['table'], | $adjust['table'], | ||||
$adjust['unique'] ? 'UNIQUE' : '/* NONUNIQUE */', | $key_name, | ||||
$adjust['name'], | |||||
implode(', ', $adjust['columns'])); | implode(', ', $adjust['columns'])); | ||||
} | } | ||||
break; | break; | ||||
default: | default: | ||||
throw new Exception( | throw new Exception( | ||||
pht('Unknown schema adjustment kind "%s"!', $adjust['kind'])); | pht('Unknown schema adjustment kind "%s"!', $adjust['kind'])); | ||||
} | } | ||||
} catch (AphrontQueryException $ex) { | } catch (AphrontQueryException $ex) { | ||||
Show All 13 Lines | private function adjustSchemata($force) { | ||||
$table = id(new PhutilConsoleTable()) | $table = id(new PhutilConsoleTable()) | ||||
->addColumn('target', array('title' => pht('Target'))) | ->addColumn('target', array('title' => pht('Target'))) | ||||
->addColumn('error', array('title' => pht('Error'))); | ->addColumn('error', array('title' => pht('Error'))); | ||||
foreach ($failed as $failure) { | foreach ($failed as $failure) { | ||||
list($adjust, $ex) = $failure; | list($adjust, $ex) = $failure; | ||||
$pieces = array_select_keys($adjust, array('database', 'table', 'naeme')); | $pieces = array_select_keys($adjust, array('database', 'table', 'name')); | ||||
Not Done Inline Actionsderpalerp epriestley: derpalerp | |||||
$pieces = array_filter($pieces); | $pieces = array_filter($pieces); | ||||
$target = implode('.', $pieces); | $target = implode('.', $pieces); | ||||
$table->addRow( | $table->addRow( | ||||
array( | array( | ||||
'target' => $target, | 'target' => $target, | ||||
'error' => $ex->getMessage(), | 'error' => $ex->getMessage(), | ||||
)); | )); | ||||
Show All 13 Lines | private function findAdjustments() { | ||||
$issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET; | $issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET; | ||||
$issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION; | $issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION; | ||||
$issue_columntype = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; | $issue_columntype = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; | ||||
$issue_surpluskey = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY; | $issue_surpluskey = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY; | ||||
$issue_missingkey = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY; | $issue_missingkey = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY; | ||||
$issue_columns = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS; | $issue_columns = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS; | ||||
$issue_unique = PhabricatorConfigStorageSchema::ISSUE_UNIQUE; | $issue_unique = PhabricatorConfigStorageSchema::ISSUE_UNIQUE; | ||||
$issue_longkey = PhabricatorConfigStorageSchema::ISSUE_LONGKEY; | |||||
$adjustments = array(); | $adjustments = array(); | ||||
foreach ($comp->getDatabases() as $database_name => $database) { | foreach ($comp->getDatabases() as $database_name => $database) { | ||||
$expect_database = $expect->getDatabase($database_name); | $expect_database = $expect->getDatabase($database_name); | ||||
$actual_database = $actual->getDatabase($database_name); | $actual_database = $actual->getDatabase($database_name); | ||||
if (!$expect_database || !$actual_database) { | if (!$expect_database || !$actual_database) { | ||||
// If there's a real issue here, skip this stuff. | // If there's a real issue here, skip this stuff. | ||||
▲ Show 20 Lines • Show All 107 Lines • ▼ Show 20 Lines | foreach ($comp->getDatabases() as $database_name => $database) { | ||||
if ($key->hasIssue($issue_columns)) { | if ($key->hasIssue($issue_columns)) { | ||||
$issues[] = $issue_columns; | $issues[] = $issue_columns; | ||||
} | } | ||||
if ($key->hasIssue($issue_unique)) { | if ($key->hasIssue($issue_unique)) { | ||||
$issues[] = $issue_unique; | $issues[] = $issue_unique; | ||||
} | } | ||||
// NOTE: We can't really fix this, per se, but we may need to remove | |||||
// the key to change the column type. In the best case, the new | |||||
// column type won't be overlong and recreating the key really will | |||||
// fix the issue. In the worst case, we get the right column type and | |||||
// lose the key, which is still better than retaining the key having | |||||
// the wrong column type. | |||||
if ($key->hasIssue($issue_longkey)) { | |||||
$issues[] = $issue_longkey; | |||||
} | |||||
if ($issues) { | if ($issues) { | ||||
$adjustment = array( | $adjustment = array( | ||||
'kind' => 'key', | 'kind' => 'key', | ||||
'database' => $database_name, | 'database' => $database_name, | ||||
'table' => $table_name, | 'table' => $table_name, | ||||
'name' => $key_name, | 'name' => $key_name, | ||||
'issues' => $issues, | 'issues' => $issues, | ||||
'exists' => (bool)$actual_key, | 'exists' => (bool)$actual_key, | ||||
'keep' => $keep_key, | 'keep' => $keep_key, | ||||
); | ); | ||||
if ($keep_key) { | if ($keep_key) { | ||||
$adjustment += array( | $adjustment += array( | ||||
'columns' => $expect_key->getColumnNames(), | 'columns' => $expect_key->getColumnNames(), | ||||
'unique' => $expect_key->getUnique(), | 'unique' => $expect_key->getUnique(), | ||||
'indexType' => $expect_key->getIndexType(), | |||||
); | ); | ||||
} | } | ||||
$adjustments[] = $adjustment; | $adjustments[] = $adjustment; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return $adjustments; | return $adjustments; | ||||
} | } | ||||
} | } |
Various keys need different grammar.