@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.

Do a better job of handling spec errors during schema adjustment

Summary:
Ref T1191. Currently if a developer forgot to specify a column type, `storage adjust` aborts explosively mid-stream. Instead:

- Make this a formal error with an unambiugous name/description instead of something you sort of infer by seeing "<unknown>".
- Make this error prevent generation of adjustment warnings, so we don't try to `ALTER TABLE t CHANGE COLUMN c <unknown>`, which is nonsense.
- When schemata errors exist, surface them prominiently in `storage adjust`.

Overall:

- Once `storage upgrade` runs `storage adjust` automatically (soon), this will make it relatively difficult to miss these errors.
- Letting these errors slip through no longer escalates into a more severe issue.

Test Plan:
Commented out the recent `mailKey` spec and ran `storage adjust`:

```
$ ./bin/storage adjust --force
Verifying database schemata...
Found no adjustments for schemata.

Target Error
phabricator2_phriction.phriction_document.mailKey Column Has No Specification

SCHEMATA ERRORS

The schemata have serious errors (detailed above) which the adjustment
workflow can not fix.

If you are not developing Phabricator itself, report this issue to the
upstream.

If you are developing Phabricator, these errors usually indicate that your
schema specifications do not agree with the schemata your code actually
builds.
```

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1191

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

+161 -49
+20 -14
src/applications/config/schema/PhabricatorConfigColumnSchema.php
··· 125 125 PhabricatorConfigStorageSchema $expect) { 126 126 127 127 $issues = array(); 128 - if ($this->getCharacterSet() != $expect->getCharacterSet()) { 129 - $issues[] = self::ISSUE_CHARSET; 130 - } 131 128 132 - if ($this->getCollation() != $expect->getCollation()) { 133 - $issues[] = self::ISSUE_COLLATION; 134 - } 129 + $type_unknown = PhabricatorConfigSchemaSpec::DATATYPE_UNKNOWN; 130 + if ($expect->getColumnType() == $type_unknown) { 131 + $issues[] = self::ISSUE_UNKNOWN; 132 + } else { 133 + if ($this->getCharacterSet() != $expect->getCharacterSet()) { 134 + $issues[] = self::ISSUE_CHARSET; 135 + } 135 136 136 - if ($this->getColumnType() != $expect->getColumnType()) { 137 - $issues[] = self::ISSUE_COLUMNTYPE; 138 - } 137 + if ($this->getCollation() != $expect->getCollation()) { 138 + $issues[] = self::ISSUE_COLLATION; 139 + } 139 140 140 - if ($this->getNullable() !== $expect->getNullable()) { 141 - $issues[] = self::ISSUE_NULLABLE; 142 - } 141 + if ($this->getColumnType() != $expect->getColumnType()) { 142 + $issues[] = self::ISSUE_COLUMNTYPE; 143 + } 144 + 145 + if ($this->getNullable() !== $expect->getNullable()) { 146 + $issues[] = self::ISSUE_NULLABLE; 147 + } 143 148 144 - if ($this->getAutoIncrement() !== $expect->getAutoIncrement()) { 145 - $issues[] = self::ISSUE_AUTOINCREMENT; 149 + if ($this->getAutoIncrement() !== $expect->getAutoIncrement()) { 150 + $issues[] = self::ISSUE_AUTOINCREMENT; 151 + } 146 152 } 147 153 148 154 return $issues;
+5 -3
src/applications/config/schema/PhabricatorConfigSchemaSpec.php
··· 7 7 private $utf8BinaryCollation; 8 8 private $utf8SortingCollation; 9 9 10 + const DATATYPE_UNKNOWN = '<unknown>'; 11 + 10 12 public function setUTF8SortingCollation($utf8_sorting_collation) { 11 13 $this->utf8SortingCollation = $utf8_sorting_collation; 12 14 return $this; ··· 324 326 $column_type = 'date'; 325 327 break; 326 328 default: 327 - $column_type = pht('<unknown>'); 328 - $charset = pht('<unknown>'); 329 - $collation = pht('<unknown>'); 329 + $column_type = self::DATATYPE_UNKNOWN; 330 + $charset = self::DATATYPE_UNKNOWN; 331 + $collation = self::DATATYPE_UNKNOWN; 330 332 break; 331 333 } 332 334 }
+6
src/applications/config/schema/PhabricatorConfigStorageSchema.php
··· 16 16 const ISSUE_SUBWARN = 'subwarn'; 17 17 const ISSUE_SUBFAIL = 'subfail'; 18 18 const ISSUE_AUTOINCREMENT = 'autoincrement'; 19 + const ISSUE_UNKNOWN = 'unknown'; 19 20 20 21 const STATUS_OKAY = 'okay'; 21 22 const STATUS_WARN = 'warn'; ··· 127 128 return pht('Subschemata Have Failures'); 128 129 case self::ISSUE_AUTOINCREMENT: 129 130 return pht('Column has Wrong Autoincrement'); 131 + case self::ISSUE_UNKNOWN: 132 + return pht('Column Has No Specification'); 130 133 default: 131 134 throw new Exception(pht('Unknown schema issue "%s"!', $issue)); 132 135 } ··· 162 165 return pht('Subschemata have setup failures.'); 163 166 case self::ISSUE_AUTOINCREMENT: 164 167 return pht('This column has the wrong autoincrement setting.'); 168 + case self::ISSUE_UNKNOWN: 169 + return pht('This column is missing a type specification.'); 165 170 default: 166 171 throw new Exception(pht('Unknown schema issue "%s"!', $issue)); 167 172 } ··· 173 178 case self::ISSUE_SURPLUS: 174 179 case self::ISSUE_NULLABLE: 175 180 case self::ISSUE_SUBFAIL: 181 + case self::ISSUE_UNKNOWN: 176 182 return self::STATUS_FAIL; 177 183 case self::ISSUE_SUBWARN: 178 184 case self::ISSUE_COLUMNTYPE:
+1 -1
src/infrastructure/storage/lisk/LiskDAO.php
··· 1804 1804 } 1805 1805 1806 1806 // We don't know the type of this column. 1807 - $map[$property] = '<unknown>'; 1807 + $map[$property] = PhabricatorConfigSchemaSpec::DATATYPE_UNKNOWN; 1808 1808 } 1809 1809 1810 1810 return $map;
+129 -31
src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php
··· 78 78 "%s\n", 79 79 pht('Verifying database schemata...')); 80 80 81 - $adjustments = $this->findAdjustments(); 81 + list($adjustments, $errors) = $this->findAdjustments(); 82 82 $api = $this->getAPI(); 83 83 84 84 if (!$adjustments) { 85 85 $console->writeOut( 86 86 "%s\n", 87 - pht('Found no issues with schemata.')); 88 - return; 87 + pht('Found no adjustments for schemata.')); 88 + 89 + return $this->printErrors($errors, 0); 89 90 } 90 91 91 92 if (!$force && !$api->isCharacterSetAvailable('utf8mb4')) { ··· 343 344 $console->writeOut( 344 345 "%s\n", 345 346 pht('Completed fixing all schema issues.')); 346 - return 0; 347 - } 347 + 348 + $err = 0; 349 + } else { 350 + $table = id(new PhutilConsoleTable()) 351 + ->addColumn('target', array('title' => pht('Target'))) 352 + ->addColumn('error', array('title' => pht('Error'))); 353 + 354 + foreach ($failed as $failure) { 355 + list($adjust, $ex) = $failure; 348 356 349 - $table = id(new PhutilConsoleTable()) 350 - ->addColumn('target', array('title' => pht('Target'))) 351 - ->addColumn('error', array('title' => pht('Error'))); 357 + $pieces = array_select_keys( 358 + $adjust, 359 + array('database', 'table', 'name')); 360 + $pieces = array_filter($pieces); 361 + $target = implode('.', $pieces); 352 362 353 - foreach ($failed as $failure) { 354 - list($adjust, $ex) = $failure; 363 + $table->addRow( 364 + array( 365 + 'target' => $target, 366 + 'error' => $ex->getMessage(), 367 + )); 368 + } 355 369 356 - $pieces = array_select_keys($adjust, array('database', 'table', 'name')); 357 - $pieces = array_filter($pieces); 358 - $target = implode('.', $pieces); 370 + $console->writeOut("\n"); 371 + $table->draw(); 372 + $console->writeOut( 373 + "\n%s\n", 374 + pht('Failed to make some schema adjustments, detailed above.')); 375 + $console->writeOut( 376 + "%s\n", 377 + pht( 378 + 'For help troubleshooting adjustments, see "Managing Storage '. 379 + 'Adjustments" in the documentation.')); 359 380 360 - $table->addRow( 361 - array( 362 - 'target' => $target, 363 - 'error' => $ex->getMessage(), 364 - )); 381 + $err = 1; 365 382 } 366 383 367 - $console->writeOut("\n"); 368 - $table->draw(); 369 - $console->writeOut( 370 - "\n%s\n", 371 - pht('Failed to make some schema adjustments, detailed above.')); 372 - $console->writeOut( 373 - "%s\n", 374 - pht( 375 - 'For help troubleshooting adjustments, see "Managing Storage '. 376 - 'Adjustments" in the documentation.')); 377 - 378 - return 1; 384 + return $this->printErrors($errors, $err); 379 385 } 380 386 381 387 private function findAdjustments() { ··· 392 398 $issue_auto = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT; 393 399 394 400 $adjustments = array(); 401 + $errors = array(); 395 402 foreach ($comp->getDatabases() as $database_name => $database) { 403 + foreach ($this->findErrors($database) as $issue) { 404 + $errors[] = array( 405 + 'database' => $database_name, 406 + 'issue' => $issue, 407 + ); 408 + } 409 + 396 410 $expect_database = $expect->getDatabase($database_name); 397 411 $actual_database = $actual->getDatabase($database_name); 398 412 ··· 420 434 } 421 435 422 436 foreach ($database->getTables() as $table_name => $table) { 437 + foreach ($this->findErrors($table) as $issue) { 438 + $errors[] = array( 439 + 'database' => $database_name, 440 + 'table' => $table_name, 441 + 'issue' => $issue, 442 + ); 443 + } 444 + 423 445 $expect_table = $expect_database->getTable($table_name); 424 446 $actual_table = $actual_database->getTable($table_name); 425 447 ··· 443 465 } 444 466 445 467 foreach ($table->getColumns() as $column_name => $column) { 468 + foreach ($this->findErrors($column) as $issue) { 469 + $errors[] = array( 470 + 'database' => $database_name, 471 + 'table' => $table_name, 472 + 'name' => $column_name, 473 + 'issue' => $issue, 474 + ); 475 + } 476 + 446 477 $expect_column = $expect_table->getColumn($column_name); 447 478 $actual_column = $actual_table->getColumn($column_name); 448 479 ··· 503 534 } 504 535 505 536 foreach ($table->getKeys() as $key_name => $key) { 537 + foreach ($this->findErrors($key) as $issue) { 538 + $errors[] = array( 539 + 'database' => $database_name, 540 + 'table' => $table_name, 541 + 'name' => $key_name, 542 + 'issue' => $issue, 543 + ); 544 + } 545 + 506 546 $expect_key = $expect_table->getKey($key_name); 507 547 $actual_key = $actual_table->getKey($key_name); 508 548 ··· 560 600 } 561 601 } 562 602 563 - return $adjustments; 603 + return array($adjustments, $errors); 564 604 } 565 605 606 + private function findErrors(PhabricatorConfigStorageSchema $schema) { 607 + $result = array(); 608 + foreach ($schema->getLocalIssues() as $issue) { 609 + $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); 610 + if ($status == PhabricatorConfigStorageSchema::STATUS_FAIL) { 611 + $result[] = $issue; 612 + } 613 + } 614 + return $result; 615 + } 616 + 617 + private function printErrors(array $errors, $default_return) { 618 + if (!$errors) { 619 + return $default_return; 620 + } 621 + 622 + $console = PhutilConsole::getConsole(); 623 + 624 + $table = id(new PhutilConsoleTable()) 625 + ->addColumn('target', array('title' => pht('Target'))) 626 + ->addColumn('error', array('title' => pht('Error'))); 627 + 628 + foreach ($errors as $error) { 629 + $pieces = array_select_keys( 630 + $error, 631 + array('database', 'table', 'name')); 632 + $pieces = array_filter($pieces); 633 + $target = implode('.', $pieces); 634 + 635 + $name = PhabricatorConfigStorageSchema::getIssueName($error['issue']); 636 + 637 + $table->addRow( 638 + array( 639 + 'target' => $target, 640 + 'error' => $name, 641 + )); 642 + } 643 + 644 + $console->writeOut("\n"); 645 + $table->draw(); 646 + $console->writeOut("\n"); 647 + 648 + $message = pht( 649 + "The schemata have serious errors (detailed above) which the adjustment ". 650 + "workflow can not fix.\n\n". 651 + "If you are not developing Phabricator itself, report this issue to ". 652 + "the upstream.\n\n". 653 + "If you are developing Phabricator, these errors usually indicate that ". 654 + "your schema specifications do not agree with the schemata your code ". 655 + "actually builds."); 656 + 657 + $console->writeOut( 658 + "**<bg:red> %s </bg>**\n\n%s\n", 659 + pht('SCHEMATA ERRORS'), 660 + phutil_console_wrap($message)); 661 + 662 + return 2; 663 + } 566 664 567 665 }