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

Load all keys, support unique keys, and provide an "all issues" view

Summary:
Ref T1191. Three parts:

- The old way of getting key information only got primary / unique / foreign keys, not all keys. Use `SHOW INDEXES` to get all keys instead.
- Track key uniqueness and raise warnings about it.
- Add a new "all issues" view to show an expanded, flat view of all issues. This is just an easier way to get a list so you don't have to dig around in the hierarchical view.

Test Plan:
{F206351}

{F206352}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1191

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

+958 -734
+4
src/__phutil_library_map__.php
··· 1344 1344 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', 1345 1345 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', 1346 1346 'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php', 1347 + 'PhabricatorConfigDatabaseIssueController' => 'applications/config/controller/PhabricatorConfigDatabaseIssueController.php', 1347 1348 'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php', 1348 1349 'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php', 1350 + 'PhabricatorConfigDatabaseStatusController' => 'applications/config/controller/PhabricatorConfigDatabaseStatusController.php', 1349 1351 'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php', 1350 1352 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', 1351 1353 'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php', ··· 4261 4263 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 4262 4264 'PhabricatorConfigController' => 'PhabricatorController', 4263 4265 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', 4266 + 'PhabricatorConfigDatabaseIssueController' => 'PhabricatorConfigDatabaseController', 4264 4267 'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema', 4265 4268 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', 4269 + 'PhabricatorConfigDatabaseStatusController' => 'PhabricatorConfigDatabaseController', 4266 4270 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 4267 4271 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 4268 4272 'PhabricatorConfigEditController' => 'PhabricatorConfigController',
+2 -1
src/applications/config/application/PhabricatorConfigApplication.php
··· 46 46 '(?:(?P<database>[^/]+)/'. 47 47 '(?:(?P<table>[^/]+)/'. 48 48 '(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?' 49 - => 'PhabricatorConfigDatabaseController', 49 + => 'PhabricatorConfigDatabaseStatusController', 50 + 'dbissue/' => 'PhabricatorConfigDatabaseIssueController', 50 51 '(?P<verb>ignore|unignore)/(?P<key>[^/]+)/' 51 52 => 'PhabricatorConfigIgnoreController', 52 53 'issue/' => array(
+1
src/applications/config/controller/PhabricatorConfigController.php
··· 18 18 $nav->addFilter('issue/', pht('Setup Issues')); 19 19 $nav->addLabel(pht('Database')); 20 20 $nav->addFilter('database/', pht('Database Status')); 21 + $nav->addFilter('dbissue/', pht('Database Issues')); 21 22 $nav->addLabel(pht('Welcome')); 22 23 $nav->addFilter('welcome/', pht('Welcome Screen')); 23 24
+6 -715
src/applications/config/controller/PhabricatorConfigDatabaseController.php
··· 1 1 <?php 2 2 3 - final class PhabricatorConfigDatabaseController 3 + abstract class PhabricatorConfigDatabaseController 4 4 extends PhabricatorConfigController { 5 5 6 6 const MAX_INNODB_KEY_LENGTH = 767; 7 7 8 - private $database; 9 - private $table; 10 - private $column; 11 - private $key; 12 - 13 - public function willProcessRequest(array $data) { 14 - $this->database = idx($data, 'database'); 15 - $this->table = idx($data, 'table'); 16 - $this->column = idx($data, 'column'); 17 - $this->key = idx($data, 'key'); 18 - } 19 - 20 - public function processRequest() { 21 - $request = $this->getRequest(); 22 - $viewer = $request->getUser(); 23 - 8 + protected function buildSchemaQuery() { 24 9 $conf = PhabricatorEnv::newObjectFromConfig( 25 10 'mysql.configuration-provider', 26 11 array($dao = null, 'w')); ··· 35 20 $query = id(new PhabricatorConfigSchemaQuery()) 36 21 ->setAPI($api); 37 22 38 - $actual = $query->loadActualSchema(); 39 - $expect = $query->loadExpectedSchema(); 40 - $comp = $query->buildComparisonSchema($expect, $actual); 41 - 42 - if ($this->column) { 43 - return $this->renderColumn( 44 - $comp, 45 - $expect, 46 - $actual, 47 - $this->database, 48 - $this->table, 49 - $this->column); 50 - } else if ($this->key) { 51 - return $this->renderKey( 52 - $comp, 53 - $expect, 54 - $actual, 55 - $this->database, 56 - $this->table, 57 - $this->key); 58 - } else if ($this->table) { 59 - return $this->renderTable( 60 - $comp, 61 - $expect, 62 - $actual, 63 - $this->database, 64 - $this->table); 65 - } else if ($this->database) { 66 - return $this->renderDatabase( 67 - $comp, 68 - $expect, 69 - $actual, 70 - $this->database); 71 - } else { 72 - return $this->renderServer( 73 - $comp, 74 - $expect, 75 - $actual); 76 - } 23 + return $query; 77 24 } 78 25 79 - private function buildResponse($title, $body) { 80 - $nav = $this->buildSideNavView(); 81 - $nav->selectFilter('database/'); 82 - 83 - $crumbs = $this->buildApplicationCrumbs(); 84 - if ($this->database) { 85 - $crumbs->addTextCrumb( 86 - pht('Database Status'), 87 - $this->getApplicationURI('database/')); 88 - if ($this->table) { 89 - $crumbs->addTextCrumb( 90 - $this->database, 91 - $this->getApplicationURI('database/'.$this->database.'/')); 92 - if ($this->column || $this->key) { 93 - $crumbs->addTextCrumb( 94 - $this->table, 95 - $this->getApplicationURI( 96 - 'database/'.$this->database.'/'.$this->table.'/')); 97 - if ($this->column) { 98 - $crumbs->addTextCrumb($this->column); 99 - } else { 100 - $crumbs->addTextCrumb($this->key); 101 - } 102 - } else { 103 - $crumbs->addTextCrumb($this->table); 104 - } 105 - } else { 106 - $crumbs->addTextCrumb($this->database); 107 - } 108 - } else { 109 - $crumbs->addTextCrumb(pht('Database Status')); 110 - } 111 - 112 - $nav->setCrumbs($crumbs); 113 - $nav->appendChild($body); 114 - 115 - return $this->buildApplicationPage( 116 - $nav, 117 - array( 118 - 'title' => $title, 119 - )); 120 - } 121 - 122 - 123 - private function renderServer( 124 - PhabricatorConfigServerSchema $comp, 125 - PhabricatorConfigServerSchema $expect, 126 - PhabricatorConfigServerSchema $actual) { 127 - 128 - $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 129 - $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 130 - 131 - $rows = array(); 132 - foreach ($comp->getDatabases() as $database_name => $database) { 133 - $actual_database = $actual->getDatabase($database_name); 134 - if ($actual_database) { 135 - $charset = $actual_database->getCharacterSet(); 136 - $collation = $actual_database->getCollation(); 137 - } else { 138 - $charset = null; 139 - $collation = null; 140 - } 141 - 142 - $status = $database->getStatus(); 143 - $issues = $database->getIssues(); 144 - 145 - $rows[] = array( 146 - $this->renderIcon($status), 147 - phutil_tag( 148 - 'a', 149 - array( 150 - 'href' => $this->getApplicationURI( 151 - '/database/'.$database_name.'/'), 152 - ), 153 - $database_name), 154 - $this->renderAttr($charset, $database->hasIssue($charset_issue)), 155 - $this->renderAttr($collation, $database->hasIssue($collation_issue)), 156 - ); 157 - } 158 - 159 - $table = id(new AphrontTableView($rows)) 160 - ->setHeaders( 161 - array( 162 - null, 163 - pht('Database'), 164 - pht('Charset'), 165 - pht('Collation'), 166 - )) 167 - ->setColumnClasses( 168 - array( 169 - null, 170 - 'wide pri', 171 - null, 172 - null, 173 - )); 174 - 175 - $title = pht('Database Status'); 176 - 177 - $properties = $this->buildProperties( 178 - array( 179 - ), 180 - $comp->getIssues()); 181 - 182 - $box = id(new PHUIObjectBoxView()) 183 - ->setHeaderText($title) 184 - ->addPropertyList($properties) 185 - ->appendChild($table); 186 - 187 - return $this->buildResponse($title, $box); 188 - } 189 - 190 - private function renderDatabase( 191 - PhabricatorConfigServerSchema $comp, 192 - PhabricatorConfigServerSchema $expect, 193 - PhabricatorConfigServerSchema $actual, 194 - $database_name) { 195 - 196 - $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 197 - 198 - $database = $comp->getDatabase($database_name); 199 - if (!$database) { 200 - return new Aphront404Response(); 201 - } 202 - 203 - $rows = array(); 204 - foreach ($database->getTables() as $table_name => $table) { 205 - $status = $table->getStatus(); 206 - 207 - $rows[] = array( 208 - $this->renderIcon($status), 209 - phutil_tag( 210 - 'a', 211 - array( 212 - 'href' => $this->getApplicationURI( 213 - '/database/'.$database_name.'/'.$table_name.'/'), 214 - ), 215 - $table_name), 216 - $this->renderAttr( 217 - $table->getCollation(), 218 - $table->hasIssue($collation_issue)), 219 - ); 220 - } 221 - 222 - $table = id(new AphrontTableView($rows)) 223 - ->setHeaders( 224 - array( 225 - null, 226 - pht('Table'), 227 - pht('Collation'), 228 - )) 229 - ->setColumnClasses( 230 - array( 231 - null, 232 - 'wide pri', 233 - null, 234 - )); 235 - 236 - $title = pht('Database Status: %s', $database_name); 237 - 238 - $actual_database = $actual->getDatabase($database_name); 239 - if ($actual_database) { 240 - $actual_charset = $actual_database->getCharacterSet(); 241 - $actual_collation = $actual_database->getCollation(); 242 - } else { 243 - $actual_charset = null; 244 - $actual_collation = null; 245 - } 246 - 247 - $expect_database = $expect->getDatabase($database_name); 248 - if ($expect_database) { 249 - $expect_charset = $expect_database->getCharacterSet(); 250 - $expect_collation = $expect_database->getCollation(); 251 - } else { 252 - $expect_charset = null; 253 - $expect_collation = null; 254 - } 255 - 256 - $properties = $this->buildProperties( 257 - array( 258 - array( 259 - pht('Character Set'), 260 - $actual_charset, 261 - ), 262 - array( 263 - pht('Expected Character Set'), 264 - $expect_charset, 265 - ), 266 - array( 267 - pht('Collation'), 268 - $actual_collation, 269 - ), 270 - array( 271 - pht('Expected Collation'), 272 - $expect_collation, 273 - ), 274 - ), 275 - $database->getIssues()); 276 - 277 - $box = id(new PHUIObjectBoxView()) 278 - ->setHeaderText($title) 279 - ->addPropertyList($properties) 280 - ->appendChild($table); 281 - 282 - return $this->buildResponse($title, $box); 283 - } 284 - 285 - private function renderTable( 286 - PhabricatorConfigServerSchema $comp, 287 - PhabricatorConfigServerSchema $expect, 288 - PhabricatorConfigServerSchema $actual, 289 - $database_name, 290 - $table_name) { 291 - 292 - $type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; 293 - $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 294 - $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 295 - $nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE; 296 - 297 - $database = $comp->getDatabase($database_name); 298 - if (!$database) { 299 - return new Aphront404Response(); 300 - } 301 - 302 - $table = $database->getTable($table_name); 303 - if (!$table) { 304 - return new Aphront404Response(); 305 - } 306 - 307 - $actual_database = $actual->getDatabase($database_name); 308 - $actual_table = null; 309 - if ($actual_database) { 310 - $actual_table = $actual_database->getTable($table_name); 311 - } 312 - 313 - $expect_database = $expect->getDatabase($database_name); 314 - $expect_table = null; 315 - if ($expect_database) { 316 - $expect_table = $expect_database->getTable($table_name); 317 - } 318 - 319 - $rows = array(); 320 - foreach ($table->getColumns() as $column_name => $column) { 321 - $expect_column = null; 322 - if ($expect_table) { 323 - $expect_column = $expect_table->getColumn($column_name); 324 - } 325 - 326 - $status = $column->getStatus(); 327 - 328 - $data_type = null; 329 - if ($expect_column) { 330 - $data_type = $expect_column->getDataType(); 331 - } 332 - 333 - $rows[] = array( 334 - $this->renderIcon($status), 335 - phutil_tag( 336 - 'a', 337 - array( 338 - 'href' => $this->getApplicationURI( 339 - 'database/'. 340 - $database_name.'/'. 341 - $table_name.'/'. 342 - 'col/'. 343 - $column_name.'/'), 344 - ), 345 - $column_name), 346 - $data_type, 347 - $this->renderAttr( 348 - $column->getColumnType(), 349 - $column->hasIssue($type_issue)), 350 - $this->renderAttr( 351 - $column->getNullable() 352 - ? pht('Yes') 353 - : pht('No'), 354 - $column->hasIssue($nullable_issue)), 355 - $this->renderAttr( 356 - $column->getCharacterSet(), 357 - $column->hasIssue($charset_issue)), 358 - $this->renderAttr( 359 - $column->getCollation(), 360 - $column->hasIssue($collation_issue)), 361 - ); 362 - } 363 - 364 - $table_view = id(new AphrontTableView($rows)) 365 - ->setHeaders( 366 - array( 367 - null, 368 - pht('Column'), 369 - pht('Data Type'), 370 - pht('Column Type'), 371 - pht('Nullable'), 372 - pht('Character Set'), 373 - pht('Collation'), 374 - )) 375 - ->setColumnClasses( 376 - array( 377 - null, 378 - 'wide pri', 379 - null, 380 - null, 381 - null, 382 - null 383 - )); 384 - 385 - $key_rows = array(); 386 - foreach ($table->getKeys() as $key_name => $key) { 387 - $expect_key = null; 388 - if ($expect_table) { 389 - $expect_key = $expect_table->getKey($key_name); 390 - } 391 - 392 - $status = $key->getStatus(); 393 - 394 - $size = 0; 395 - foreach ($key->getColumnNames() as $column_name) { 396 - $column = $table->getColumn($column_name); 397 - if (!$column) { 398 - $size = 0; 399 - break; 400 - } 401 - $size += $column->getKeyByteLength(); 402 - } 403 - 404 - $size_formatted = null; 405 - if ($size) { 406 - $size_formatted = $this->renderAttr( 407 - $size, 408 - ($size > self::MAX_INNODB_KEY_LENGTH)); 409 - } 410 - 411 - $key_rows[] = array( 412 - $this->renderIcon($status), 413 - phutil_tag( 414 - 'a', 415 - array( 416 - 'href' => $this->getApplicationURI( 417 - 'database/'. 418 - $database_name.'/'. 419 - $table_name.'/'. 420 - 'key/'. 421 - $key_name.'/'), 422 - ), 423 - $key_name), 424 - implode(', ', $key->getColumnNames()), 425 - $size_formatted, 426 - ); 427 - } 428 - 429 - $keys_view = id(new AphrontTableView($key_rows)) 430 - ->setHeaders( 431 - array( 432 - null, 433 - pht('Key'), 434 - pht('Columns'), 435 - pht('Size'), 436 - )) 437 - ->setColumnClasses( 438 - array( 439 - null, 440 - 'wide pri', 441 - null, 442 - null, 443 - )); 444 - 445 - $title = pht('Database Status: %s.%s', $database_name, $table_name); 446 - 447 - if ($actual_table) { 448 - $actual_collation = $actual_table->getCollation(); 449 - } else { 450 - $actual_collation = null; 451 - } 452 - 453 - if ($expect_table) { 454 - $expect_collation = $expect_table->getCollation(); 455 - } else { 456 - $expect_collation = null; 457 - } 458 - 459 - $properties = $this->buildProperties( 460 - array( 461 - array( 462 - pht('Collation'), 463 - $actual_collation, 464 - ), 465 - array( 466 - pht('Expected Collation'), 467 - $expect_collation, 468 - ), 469 - ), 470 - $table->getIssues()); 471 - 472 - $box = id(new PHUIObjectBoxView()) 473 - ->setHeaderText($title) 474 - ->addPropertyList($properties) 475 - ->appendChild($table_view) 476 - ->appendChild($keys_view); 477 - 478 - return $this->buildResponse($title, $box); 479 - } 480 - 481 - private function renderColumn( 482 - PhabricatorConfigServerSchema $comp, 483 - PhabricatorConfigServerSchema $expect, 484 - PhabricatorConfigServerSchema $actual, 485 - $database_name, 486 - $table_name, 487 - $column_name) { 488 - 489 - $database = $comp->getDatabase($database_name); 490 - if (!$database) { 491 - return new Aphront404Response(); 492 - } 493 - 494 - $table = $database->getTable($table_name); 495 - if (!$table) { 496 - return new Aphront404Response(); 497 - } 498 - 499 - $column = $table->getColumn($column_name); 500 - if (!$column) { 501 - return new Aphront404Response(); 502 - } 503 - 504 - $actual_database = $actual->getDatabase($database_name); 505 - $actual_table = null; 506 - $actual_column = null; 507 - if ($actual_database) { 508 - $actual_table = $actual_database->getTable($table_name); 509 - if ($actual_table) { 510 - $actual_column = $actual_table->getColumn($column_name); 511 - } 512 - } 513 - 514 - $expect_database = $expect->getDatabase($database_name); 515 - $expect_table = null; 516 - $expect_column = null; 517 - if ($expect_database) { 518 - $expect_table = $expect_database->getTable($table_name); 519 - if ($expect_table) { 520 - $expect_column = $expect_table->getColumn($column_name); 521 - } 522 - } 523 - 524 - if ($actual_column) { 525 - $actual_coltype = $actual_column->getColumnType(); 526 - $actual_charset = $actual_column->getCharacterSet(); 527 - $actual_collation = $actual_column->getCollation(); 528 - $actual_nullable = $actual_column->getNullable(); 529 - } else { 530 - $actual_coltype = null; 531 - $actual_charset = null; 532 - $actual_collation = null; 533 - $actual_nullable = null; 534 - } 535 - 536 - if ($expect_column) { 537 - $data_type = $expect_column->getDataType(); 538 - $expect_coltype = $expect_column->getColumnType(); 539 - $expect_charset = $expect_column->getCharacterSet(); 540 - $expect_collation = $expect_column->getCollation(); 541 - $expect_nullable = $expect_column->getNullable(); 542 - } else { 543 - $data_type = null; 544 - $expect_coltype = null; 545 - $expect_charset = null; 546 - $expect_collation = null; 547 - $expect_nullable = null; 548 - } 549 - 550 - 551 - $title = pht( 552 - 'Database Status: %s.%s.%s', 553 - $database_name, 554 - $table_name, 555 - $column_name); 556 - 557 - $properties = $this->buildProperties( 558 - array( 559 - array( 560 - pht('Data Type'), 561 - $data_type, 562 - ), 563 - array( 564 - pht('Column Type'), 565 - $actual_coltype, 566 - ), 567 - array( 568 - pht('Expected Column Type'), 569 - $expect_coltype, 570 - ), 571 - array( 572 - pht('Character Set'), 573 - $actual_charset, 574 - ), 575 - array( 576 - pht('Expected Character Set'), 577 - $expect_charset, 578 - ), 579 - array( 580 - pht('Collation'), 581 - $actual_collation, 582 - ), 583 - array( 584 - pht('Expected Collation'), 585 - $expect_collation, 586 - ), 587 - array( 588 - pht('Nullable'), 589 - $this->getNullableString($actual_nullable), 590 - ), 591 - array( 592 - pht('Expected Nullable'), 593 - $this->getNullableString($expect_nullable), 594 - ), 595 - ), 596 - $column->getIssues()); 597 - 598 - $box = id(new PHUIObjectBoxView()) 599 - ->setHeaderText($title) 600 - ->addPropertyList($properties); 601 - 602 - return $this->buildResponse($title, $box); 603 - } 604 - 605 - private function renderKey( 606 - PhabricatorConfigServerSchema $comp, 607 - PhabricatorConfigServerSchema $expect, 608 - PhabricatorConfigServerSchema $actual, 609 - $database_name, 610 - $table_name, 611 - $key_name) { 612 - 613 - $database = $comp->getDatabase($database_name); 614 - if (!$database) { 615 - return new Aphront404Response(); 616 - } 617 - 618 - $table = $database->getTable($table_name); 619 - if (!$table) { 620 - return new Aphront404Response(); 621 - } 622 - 623 - $key = $table->getKey($key_name); 624 - if (!$key) { 625 - return new Aphront404Response(); 626 - } 627 - 628 - $actual_database = $actual->getDatabase($database_name); 629 - $actual_table = null; 630 - $actual_key = null; 631 - if ($actual_database) { 632 - $actual_table = $actual_database->getTable($table_name); 633 - if ($actual_table) { 634 - $actual_key = $actual_table->getKey($key_name); 635 - } 636 - } 637 - 638 - $expect_database = $expect->getDatabase($database_name); 639 - $expect_table = null; 640 - $expect_key = null; 641 - if ($expect_database) { 642 - $expect_table = $expect_database->getTable($table_name); 643 - if ($expect_table) { 644 - $expect_key = $expect_table->getKey($key_name); 645 - } 646 - } 647 - 648 - if ($actual_key) { 649 - $actual_columns = $actual_key->getColumnNames(); 650 - } else { 651 - $actual_columns = array(); 652 - } 653 - 654 - if ($expect_key) { 655 - $expect_columns = $expect_key->getColumnNames(); 656 - } else { 657 - $expect_columns = array(); 658 - } 659 - 660 - $title = pht( 661 - 'Database Status: %s.%s (%s)', 662 - $database_name, 663 - $table_name, 664 - $key_name); 665 - 666 - $properties = $this->buildProperties( 667 - array( 668 - array( 669 - pht('Columns'), 670 - implode(', ', $actual_columns), 671 - ), 672 - array( 673 - pht('Expected Columns'), 674 - implode(', ', $expect_columns), 675 - ), 676 - ), 677 - $key->getIssues()); 678 - 679 - $box = id(new PHUIObjectBoxView()) 680 - ->setHeaderText($title) 681 - ->addPropertyList($properties); 682 - 683 - return $this->buildResponse($title, $box); 684 - } 685 - 686 - private function renderIcon($status) { 26 + protected function renderIcon($status) { 687 27 switch ($status) { 688 28 case PhabricatorConfigStorageSchema::STATUS_OKAY: 689 29 $icon = 'fa-check-circle green'; ··· 704 44 ->setIconFont($icon); 705 45 } 706 46 707 - private function renderAttr($attr, $issue) { 47 + protected function renderAttr($attr, $issue) { 708 48 if ($issue) { 709 49 return phutil_tag( 710 50 'span', ··· 717 57 } 718 58 } 719 59 720 - private function buildProperties(array $properties, array $issues) { 721 - $view = id(new PHUIPropertyListView()) 722 - ->setUser($this->getRequest()->getUser()); 723 - 724 - foreach ($properties as $property) { 725 - list($key, $value) = $property; 726 - $view->addProperty($key, $value); 727 - } 728 - 729 - $status_view = new PHUIStatusListView(); 730 - if (!$issues) { 731 - $status_view->addItem( 732 - id(new PHUIStatusItemView()) 733 - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 734 - ->setTarget(pht('No Schema Issues'))); 735 - } else { 736 - foreach ($issues as $issue) { 737 - $note = PhabricatorConfigStorageSchema::getIssueDescription($issue); 738 - 739 - $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); 740 - switch ($status) { 741 - case PhabricatorConfigStorageSchema::STATUS_NOTE: 742 - $icon = PHUIStatusItemView::ICON_INFO; 743 - $color = 'blue'; 744 - break; 745 - case PhabricatorConfigStorageSchema::STATUS_WARN: 746 - $icon = PHUIStatusItemView::ICON_WARNING; 747 - $color = 'yellow'; 748 - break; 749 - case PhabricatorConfigStorageSchema::STATUS_FAIL: 750 - default: 751 - $icon = PHUIStatusItemView::ICON_REJECT; 752 - $color = 'red'; 753 - break; 754 - } 755 - 756 - $item = id(new PHUIStatusItemView()) 757 - ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue)) 758 - ->setIcon($icon, $color) 759 - ->setNote($note); 760 - 761 - $status_view->addItem($item); 762 - } 763 - } 764 - $view->addProperty(pht('Schema Status'), $status_view); 765 - 766 - return $view; 767 - } 768 - 769 - private function getNullableString($value) { 60 + protected function renderBoolean($value) { 770 61 if ($value === null) { 771 62 return ''; 772 63 } else if ($value === true) {
+167
src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigDatabaseIssueController 4 + extends PhabricatorConfigDatabaseController { 5 + 6 + public function processRequest() { 7 + $request = $this->getRequest(); 8 + $viewer = $request->getUser(); 9 + 10 + $query = $this->buildSchemaQuery(); 11 + 12 + $actual = $query->loadActualSchema(); 13 + $expect = $query->loadExpectedSchema(); 14 + $comp = $query->buildComparisonSchema($expect, $actual); 15 + 16 + $crumbs = $this->buildApplicationCrumbs(); 17 + $crumbs->addTextCrumb(pht('Database Issues')); 18 + 19 + // Collect all open issues. 20 + $issues = array(); 21 + foreach ($comp->getDatabases() as $database_name => $database) { 22 + foreach ($database->getLocalIssues() as $issue) { 23 + $issues[] = array( 24 + $database_name, 25 + null, 26 + null, 27 + null, 28 + $issue); 29 + } 30 + foreach ($database->getTables() as $table_name => $table) { 31 + foreach ($table->getLocalIssues() as $issue) { 32 + $issues[] = array( 33 + $database_name, 34 + $table_name, 35 + null, 36 + null, 37 + $issue); 38 + } 39 + foreach ($table->getColumns() as $column_name => $column) { 40 + foreach ($column->getLocalIssues() as $issue) { 41 + $issues[] = array( 42 + $database_name, 43 + $table_name, 44 + 'column', 45 + $column_name, 46 + $issue); 47 + } 48 + } 49 + foreach ($table->getKeys() as $key_name => $key) { 50 + foreach ($key->getLocalIssues() as $issue) { 51 + $issues[] = array( 52 + $database_name, 53 + $table_name, 54 + 'key', 55 + $key_name, 56 + $issue); 57 + } 58 + } 59 + } 60 + } 61 + 62 + 63 + // Sort all open issues so that the most severe issues appear first. 64 + $order = array(); 65 + $counts = array(); 66 + foreach ($issues as $key => $issue) { 67 + $const = $issue[4]; 68 + $status = PhabricatorConfigStorageSchema::getIssueStatus($const); 69 + $severity = PhabricatorConfigStorageSchema::getStatusSeverity($status); 70 + $order[$key] = sprintf( 71 + '~%d~%s%s%s', 72 + 9 - $severity, 73 + $issue[0], 74 + $issue[1], 75 + $issue[3]); 76 + 77 + if (empty($counts[$status])) { 78 + $counts[$status] = 0; 79 + } 80 + 81 + $counts[$status]++; 82 + } 83 + asort($order); 84 + $issues = array_select_keys($issues, array_keys($order)); 85 + 86 + 87 + // Render the issues. 88 + $rows = array(); 89 + foreach ($issues as $issue) { 90 + $const = $issue[4]; 91 + 92 + $rows[] = array( 93 + $this->renderIcon( 94 + PhabricatorConfigStorageSchema::getIssueStatus($const)), 95 + $issue[0], 96 + $issue[1], 97 + $issue[2], 98 + $issue[3], 99 + PhabricatorConfigStorageSchema::getIssueDescription($const), 100 + ); 101 + } 102 + 103 + $table = id(new AphrontTableView($rows)) 104 + ->setHeaders( 105 + array( 106 + null, 107 + pht('Database'), 108 + pht('Table'), 109 + pht('Type'), 110 + pht('Column/Key'), 111 + pht('Issue'), 112 + )) 113 + ->setColumnClasses( 114 + array( 115 + null, 116 + null, 117 + null, 118 + null, 119 + null, 120 + 'wide', 121 + )); 122 + 123 + $errors = array(); 124 + 125 + $errors[] = pht( 126 + 'IMPORTANT: This feature is in development and the information below '. 127 + 'is not accurate! Ignore it for now. See T1191.'); 128 + 129 + if (isset($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])) { 130 + $errors[] = pht( 131 + 'Detected %s serious issue(s) with the schemata.', 132 + new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])); 133 + } 134 + 135 + if (isset($counts[PhabricatorConfigStorageSchema::STATUS_WARN])) { 136 + $errors[] = pht( 137 + 'Detected %s warning(s) with the schemata.', 138 + new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_WARN])); 139 + } 140 + 141 + if (isset($counts[PhabricatorConfigStorageSchema::STATUS_NOTE])) { 142 + $errors[] = pht( 143 + 'Detected %s minor issue(s) with the scheamata.', 144 + new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_NOTE])); 145 + } 146 + 147 + $table_box = id(new PHUIObjectBoxView()) 148 + ->setHeaderText(pht('Database Issues')) 149 + ->setFormErrors($errors) 150 + ->appendChild($table); 151 + 152 + $nav = $this->buildSideNavView(); 153 + $nav->selectFilter('dbissue/'); 154 + $nav->appendChild( 155 + array( 156 + $crumbs, 157 + $table_box, 158 + )); 159 + 160 + return $this->buildApplicationPage( 161 + $nav, 162 + array( 163 + 'title' => 'all', 164 + )); 165 + } 166 + 167 + }
+737
src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigDatabaseStatusController 4 + extends PhabricatorConfigDatabaseController { 5 + 6 + private $database; 7 + private $table; 8 + private $column; 9 + private $key; 10 + 11 + public function willProcessRequest(array $data) { 12 + $this->database = idx($data, 'database'); 13 + $this->table = idx($data, 'table'); 14 + $this->column = idx($data, 'column'); 15 + $this->key = idx($data, 'key'); 16 + } 17 + 18 + public function processRequest() { 19 + $request = $this->getRequest(); 20 + $viewer = $request->getUser(); 21 + 22 + $query = $this->buildSchemaQuery(); 23 + 24 + $actual = $query->loadActualSchema(); 25 + $expect = $query->loadExpectedSchema(); 26 + $comp = $query->buildComparisonSchema($expect, $actual); 27 + 28 + if ($this->column) { 29 + return $this->renderColumn( 30 + $comp, 31 + $expect, 32 + $actual, 33 + $this->database, 34 + $this->table, 35 + $this->column); 36 + } else if ($this->key) { 37 + return $this->renderKey( 38 + $comp, 39 + $expect, 40 + $actual, 41 + $this->database, 42 + $this->table, 43 + $this->key); 44 + } else if ($this->table) { 45 + return $this->renderTable( 46 + $comp, 47 + $expect, 48 + $actual, 49 + $this->database, 50 + $this->table); 51 + } else if ($this->database) { 52 + return $this->renderDatabase( 53 + $comp, 54 + $expect, 55 + $actual, 56 + $this->database); 57 + } else { 58 + return $this->renderServer( 59 + $comp, 60 + $expect, 61 + $actual); 62 + } 63 + } 64 + 65 + private function buildResponse($title, $body) { 66 + $nav = $this->buildSideNavView(); 67 + $nav->selectFilter('database/'); 68 + 69 + $crumbs = $this->buildApplicationCrumbs(); 70 + if ($this->database) { 71 + $crumbs->addTextCrumb( 72 + pht('Database Status'), 73 + $this->getApplicationURI('database/')); 74 + if ($this->table) { 75 + $crumbs->addTextCrumb( 76 + $this->database, 77 + $this->getApplicationURI('database/'.$this->database.'/')); 78 + if ($this->column || $this->key) { 79 + $crumbs->addTextCrumb( 80 + $this->table, 81 + $this->getApplicationURI( 82 + 'database/'.$this->database.'/'.$this->table.'/')); 83 + if ($this->column) { 84 + $crumbs->addTextCrumb($this->column); 85 + } else { 86 + $crumbs->addTextCrumb($this->key); 87 + } 88 + } else { 89 + $crumbs->addTextCrumb($this->table); 90 + } 91 + } else { 92 + $crumbs->addTextCrumb($this->database); 93 + } 94 + } else { 95 + $crumbs->addTextCrumb(pht('Database Status')); 96 + } 97 + 98 + $nav->setCrumbs($crumbs); 99 + $nav->appendChild($body); 100 + 101 + return $this->buildApplicationPage( 102 + $nav, 103 + array( 104 + 'title' => $title, 105 + )); 106 + } 107 + 108 + 109 + private function renderServer( 110 + PhabricatorConfigServerSchema $comp, 111 + PhabricatorConfigServerSchema $expect, 112 + PhabricatorConfigServerSchema $actual) { 113 + 114 + $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 115 + $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 116 + 117 + $rows = array(); 118 + foreach ($comp->getDatabases() as $database_name => $database) { 119 + $actual_database = $actual->getDatabase($database_name); 120 + if ($actual_database) { 121 + $charset = $actual_database->getCharacterSet(); 122 + $collation = $actual_database->getCollation(); 123 + } else { 124 + $charset = null; 125 + $collation = null; 126 + } 127 + 128 + $status = $database->getStatus(); 129 + $issues = $database->getIssues(); 130 + 131 + $rows[] = array( 132 + $this->renderIcon($status), 133 + phutil_tag( 134 + 'a', 135 + array( 136 + 'href' => $this->getApplicationURI( 137 + '/database/'.$database_name.'/'), 138 + ), 139 + $database_name), 140 + $this->renderAttr($charset, $database->hasIssue($charset_issue)), 141 + $this->renderAttr($collation, $database->hasIssue($collation_issue)), 142 + ); 143 + } 144 + 145 + $table = id(new AphrontTableView($rows)) 146 + ->setHeaders( 147 + array( 148 + null, 149 + pht('Database'), 150 + pht('Charset'), 151 + pht('Collation'), 152 + )) 153 + ->setColumnClasses( 154 + array( 155 + null, 156 + 'wide pri', 157 + null, 158 + null, 159 + )); 160 + 161 + $title = pht('Database Status'); 162 + 163 + $properties = $this->buildProperties( 164 + array( 165 + ), 166 + $comp->getIssues()); 167 + 168 + $box = id(new PHUIObjectBoxView()) 169 + ->setHeaderText($title) 170 + ->addPropertyList($properties) 171 + ->appendChild($table); 172 + 173 + return $this->buildResponse($title, $box); 174 + } 175 + 176 + private function renderDatabase( 177 + PhabricatorConfigServerSchema $comp, 178 + PhabricatorConfigServerSchema $expect, 179 + PhabricatorConfigServerSchema $actual, 180 + $database_name) { 181 + 182 + $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 183 + 184 + $database = $comp->getDatabase($database_name); 185 + if (!$database) { 186 + return new Aphront404Response(); 187 + } 188 + 189 + $rows = array(); 190 + foreach ($database->getTables() as $table_name => $table) { 191 + $status = $table->getStatus(); 192 + 193 + $rows[] = array( 194 + $this->renderIcon($status), 195 + phutil_tag( 196 + 'a', 197 + array( 198 + 'href' => $this->getApplicationURI( 199 + '/database/'.$database_name.'/'.$table_name.'/'), 200 + ), 201 + $table_name), 202 + $this->renderAttr( 203 + $table->getCollation(), 204 + $table->hasIssue($collation_issue)), 205 + ); 206 + } 207 + 208 + $table = id(new AphrontTableView($rows)) 209 + ->setHeaders( 210 + array( 211 + null, 212 + pht('Table'), 213 + pht('Collation'), 214 + )) 215 + ->setColumnClasses( 216 + array( 217 + null, 218 + 'wide pri', 219 + null, 220 + )); 221 + 222 + $title = pht('Database Status: %s', $database_name); 223 + 224 + $actual_database = $actual->getDatabase($database_name); 225 + if ($actual_database) { 226 + $actual_charset = $actual_database->getCharacterSet(); 227 + $actual_collation = $actual_database->getCollation(); 228 + } else { 229 + $actual_charset = null; 230 + $actual_collation = null; 231 + } 232 + 233 + $expect_database = $expect->getDatabase($database_name); 234 + if ($expect_database) { 235 + $expect_charset = $expect_database->getCharacterSet(); 236 + $expect_collation = $expect_database->getCollation(); 237 + } else { 238 + $expect_charset = null; 239 + $expect_collation = null; 240 + } 241 + 242 + $properties = $this->buildProperties( 243 + array( 244 + array( 245 + pht('Character Set'), 246 + $actual_charset, 247 + ), 248 + array( 249 + pht('Expected Character Set'), 250 + $expect_charset, 251 + ), 252 + array( 253 + pht('Collation'), 254 + $actual_collation, 255 + ), 256 + array( 257 + pht('Expected Collation'), 258 + $expect_collation, 259 + ), 260 + ), 261 + $database->getIssues()); 262 + 263 + $box = id(new PHUIObjectBoxView()) 264 + ->setHeaderText($title) 265 + ->addPropertyList($properties) 266 + ->appendChild($table); 267 + 268 + return $this->buildResponse($title, $box); 269 + } 270 + 271 + private function renderTable( 272 + PhabricatorConfigServerSchema $comp, 273 + PhabricatorConfigServerSchema $expect, 274 + PhabricatorConfigServerSchema $actual, 275 + $database_name, 276 + $table_name) { 277 + 278 + $type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; 279 + $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 280 + $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 281 + $nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE; 282 + $unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE; 283 + 284 + $database = $comp->getDatabase($database_name); 285 + if (!$database) { 286 + return new Aphront404Response(); 287 + } 288 + 289 + $table = $database->getTable($table_name); 290 + if (!$table) { 291 + return new Aphront404Response(); 292 + } 293 + 294 + $actual_database = $actual->getDatabase($database_name); 295 + $actual_table = null; 296 + if ($actual_database) { 297 + $actual_table = $actual_database->getTable($table_name); 298 + } 299 + 300 + $expect_database = $expect->getDatabase($database_name); 301 + $expect_table = null; 302 + if ($expect_database) { 303 + $expect_table = $expect_database->getTable($table_name); 304 + } 305 + 306 + $rows = array(); 307 + foreach ($table->getColumns() as $column_name => $column) { 308 + $expect_column = null; 309 + if ($expect_table) { 310 + $expect_column = $expect_table->getColumn($column_name); 311 + } 312 + 313 + $status = $column->getStatus(); 314 + 315 + $data_type = null; 316 + if ($expect_column) { 317 + $data_type = $expect_column->getDataType(); 318 + } 319 + 320 + $rows[] = array( 321 + $this->renderIcon($status), 322 + phutil_tag( 323 + 'a', 324 + array( 325 + 'href' => $this->getApplicationURI( 326 + 'database/'. 327 + $database_name.'/'. 328 + $table_name.'/'. 329 + 'col/'. 330 + $column_name.'/'), 331 + ), 332 + $column_name), 333 + $data_type, 334 + $this->renderAttr( 335 + $column->getColumnType(), 336 + $column->hasIssue($type_issue)), 337 + $this->renderAttr( 338 + $this->renderBoolean($column->getNullable()), 339 + $column->hasIssue($nullable_issue)), 340 + $this->renderAttr( 341 + $column->getCharacterSet(), 342 + $column->hasIssue($charset_issue)), 343 + $this->renderAttr( 344 + $column->getCollation(), 345 + $column->hasIssue($collation_issue)), 346 + ); 347 + } 348 + 349 + $table_view = id(new AphrontTableView($rows)) 350 + ->setHeaders( 351 + array( 352 + null, 353 + pht('Column'), 354 + pht('Data Type'), 355 + pht('Column Type'), 356 + pht('Nullable'), 357 + pht('Character Set'), 358 + pht('Collation'), 359 + )) 360 + ->setColumnClasses( 361 + array( 362 + null, 363 + 'wide pri', 364 + null, 365 + null, 366 + null, 367 + null 368 + )); 369 + 370 + $key_rows = array(); 371 + foreach ($table->getKeys() as $key_name => $key) { 372 + $expect_key = null; 373 + if ($expect_table) { 374 + $expect_key = $expect_table->getKey($key_name); 375 + } 376 + 377 + $status = $key->getStatus(); 378 + 379 + $size = 0; 380 + foreach ($key->getColumnNames() as $column_name) { 381 + $column = $table->getColumn($column_name); 382 + if (!$column) { 383 + $size = 0; 384 + break; 385 + } 386 + $size += $column->getKeyByteLength(); 387 + } 388 + 389 + $size_formatted = null; 390 + if ($size) { 391 + $size_formatted = $this->renderAttr( 392 + $size, 393 + ($size > self::MAX_INNODB_KEY_LENGTH)); 394 + } 395 + 396 + $key_rows[] = array( 397 + $this->renderIcon($status), 398 + phutil_tag( 399 + 'a', 400 + array( 401 + 'href' => $this->getApplicationURI( 402 + 'database/'. 403 + $database_name.'/'. 404 + $table_name.'/'. 405 + 'key/'. 406 + $key_name.'/'), 407 + ), 408 + $key_name), 409 + implode(', ', $key->getColumnNames()), 410 + $this->renderAttr( 411 + $this->renderBoolean($key->getUnique()), 412 + $key->hasIssue($unique_issue)), 413 + $size_formatted, 414 + ); 415 + } 416 + 417 + $keys_view = id(new AphrontTableView($key_rows)) 418 + ->setHeaders( 419 + array( 420 + null, 421 + pht('Key'), 422 + pht('Columns'), 423 + pht('Unique'), 424 + pht('Size'), 425 + )) 426 + ->setColumnClasses( 427 + array( 428 + null, 429 + 'wide pri', 430 + null, 431 + null, 432 + null, 433 + )); 434 + 435 + $title = pht('Database Status: %s.%s', $database_name, $table_name); 436 + 437 + if ($actual_table) { 438 + $actual_collation = $actual_table->getCollation(); 439 + } else { 440 + $actual_collation = null; 441 + } 442 + 443 + if ($expect_table) { 444 + $expect_collation = $expect_table->getCollation(); 445 + } else { 446 + $expect_collation = null; 447 + } 448 + 449 + $properties = $this->buildProperties( 450 + array( 451 + array( 452 + pht('Collation'), 453 + $actual_collation, 454 + ), 455 + array( 456 + pht('Expected Collation'), 457 + $expect_collation, 458 + ), 459 + ), 460 + $table->getIssues()); 461 + 462 + $box = id(new PHUIObjectBoxView()) 463 + ->setHeaderText($title) 464 + ->addPropertyList($properties) 465 + ->appendChild($table_view) 466 + ->appendChild($keys_view); 467 + 468 + return $this->buildResponse($title, $box); 469 + } 470 + 471 + private function renderColumn( 472 + PhabricatorConfigServerSchema $comp, 473 + PhabricatorConfigServerSchema $expect, 474 + PhabricatorConfigServerSchema $actual, 475 + $database_name, 476 + $table_name, 477 + $column_name) { 478 + 479 + $database = $comp->getDatabase($database_name); 480 + if (!$database) { 481 + return new Aphront404Response(); 482 + } 483 + 484 + $table = $database->getTable($table_name); 485 + if (!$table) { 486 + return new Aphront404Response(); 487 + } 488 + 489 + $column = $table->getColumn($column_name); 490 + if (!$column) { 491 + return new Aphront404Response(); 492 + } 493 + 494 + $actual_database = $actual->getDatabase($database_name); 495 + $actual_table = null; 496 + $actual_column = null; 497 + if ($actual_database) { 498 + $actual_table = $actual_database->getTable($table_name); 499 + if ($actual_table) { 500 + $actual_column = $actual_table->getColumn($column_name); 501 + } 502 + } 503 + 504 + $expect_database = $expect->getDatabase($database_name); 505 + $expect_table = null; 506 + $expect_column = null; 507 + if ($expect_database) { 508 + $expect_table = $expect_database->getTable($table_name); 509 + if ($expect_table) { 510 + $expect_column = $expect_table->getColumn($column_name); 511 + } 512 + } 513 + 514 + if ($actual_column) { 515 + $actual_coltype = $actual_column->getColumnType(); 516 + $actual_charset = $actual_column->getCharacterSet(); 517 + $actual_collation = $actual_column->getCollation(); 518 + $actual_nullable = $actual_column->getNullable(); 519 + } else { 520 + $actual_coltype = null; 521 + $actual_charset = null; 522 + $actual_collation = null; 523 + $actual_nullable = null; 524 + } 525 + 526 + if ($expect_column) { 527 + $data_type = $expect_column->getDataType(); 528 + $expect_coltype = $expect_column->getColumnType(); 529 + $expect_charset = $expect_column->getCharacterSet(); 530 + $expect_collation = $expect_column->getCollation(); 531 + $expect_nullable = $expect_column->getNullable(); 532 + } else { 533 + $data_type = null; 534 + $expect_coltype = null; 535 + $expect_charset = null; 536 + $expect_collation = null; 537 + $expect_nullable = null; 538 + } 539 + 540 + 541 + $title = pht( 542 + 'Database Status: %s.%s.%s', 543 + $database_name, 544 + $table_name, 545 + $column_name); 546 + 547 + $properties = $this->buildProperties( 548 + array( 549 + array( 550 + pht('Data Type'), 551 + $data_type, 552 + ), 553 + array( 554 + pht('Column Type'), 555 + $actual_coltype, 556 + ), 557 + array( 558 + pht('Expected Column Type'), 559 + $expect_coltype, 560 + ), 561 + array( 562 + pht('Character Set'), 563 + $actual_charset, 564 + ), 565 + array( 566 + pht('Expected Character Set'), 567 + $expect_charset, 568 + ), 569 + array( 570 + pht('Collation'), 571 + $actual_collation, 572 + ), 573 + array( 574 + pht('Expected Collation'), 575 + $expect_collation, 576 + ), 577 + array( 578 + pht('Nullable'), 579 + $this->renderBoolean($actual_nullable), 580 + ), 581 + array( 582 + pht('Expected Nullable'), 583 + $this->renderBoolean($expect_nullable), 584 + ), 585 + ), 586 + $column->getIssues()); 587 + 588 + $box = id(new PHUIObjectBoxView()) 589 + ->setHeaderText($title) 590 + ->addPropertyList($properties); 591 + 592 + return $this->buildResponse($title, $box); 593 + } 594 + 595 + private function renderKey( 596 + PhabricatorConfigServerSchema $comp, 597 + PhabricatorConfigServerSchema $expect, 598 + PhabricatorConfigServerSchema $actual, 599 + $database_name, 600 + $table_name, 601 + $key_name) { 602 + 603 + $database = $comp->getDatabase($database_name); 604 + if (!$database) { 605 + return new Aphront404Response(); 606 + } 607 + 608 + $table = $database->getTable($table_name); 609 + if (!$table) { 610 + return new Aphront404Response(); 611 + } 612 + 613 + $key = $table->getKey($key_name); 614 + if (!$key) { 615 + return new Aphront404Response(); 616 + } 617 + 618 + $actual_database = $actual->getDatabase($database_name); 619 + $actual_table = null; 620 + $actual_key = null; 621 + if ($actual_database) { 622 + $actual_table = $actual_database->getTable($table_name); 623 + if ($actual_table) { 624 + $actual_key = $actual_table->getKey($key_name); 625 + } 626 + } 627 + 628 + $expect_database = $expect->getDatabase($database_name); 629 + $expect_table = null; 630 + $expect_key = null; 631 + if ($expect_database) { 632 + $expect_table = $expect_database->getTable($table_name); 633 + if ($expect_table) { 634 + $expect_key = $expect_table->getKey($key_name); 635 + } 636 + } 637 + 638 + if ($actual_key) { 639 + $actual_columns = $actual_key->getColumnNames(); 640 + $actual_unique = $actual_key->getUnique(); 641 + } else { 642 + $actual_columns = array(); 643 + $actual_unique = null; 644 + } 645 + 646 + if ($expect_key) { 647 + $expect_columns = $expect_key->getColumnNames(); 648 + $expect_unique = $expect_key->getUnique(); 649 + } else { 650 + $expect_columns = array(); 651 + $expect_unique = null; 652 + } 653 + 654 + $title = pht( 655 + 'Database Status: %s.%s (%s)', 656 + $database_name, 657 + $table_name, 658 + $key_name); 659 + 660 + $properties = $this->buildProperties( 661 + array( 662 + array( 663 + pht('Unique'), 664 + $this->renderBoolean($actual_unique), 665 + ), 666 + array( 667 + pht('Expected Unique'), 668 + $this->renderBoolean($expect_unique), 669 + ), 670 + array( 671 + pht('Columns'), 672 + implode(', ', $actual_columns), 673 + ), 674 + array( 675 + pht('Expected Columns'), 676 + implode(', ', $expect_columns), 677 + ), 678 + ), 679 + $key->getIssues()); 680 + 681 + $box = id(new PHUIObjectBoxView()) 682 + ->setHeaderText($title) 683 + ->addPropertyList($properties); 684 + 685 + return $this->buildResponse($title, $box); 686 + } 687 + 688 + private function buildProperties(array $properties, array $issues) { 689 + $view = id(new PHUIPropertyListView()) 690 + ->setUser($this->getRequest()->getUser()); 691 + 692 + foreach ($properties as $property) { 693 + list($key, $value) = $property; 694 + $view->addProperty($key, $value); 695 + } 696 + 697 + $status_view = new PHUIStatusListView(); 698 + if (!$issues) { 699 + $status_view->addItem( 700 + id(new PHUIStatusItemView()) 701 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 702 + ->setTarget(pht('No Schema Issues'))); 703 + } else { 704 + foreach ($issues as $issue) { 705 + $note = PhabricatorConfigStorageSchema::getIssueDescription($issue); 706 + 707 + $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); 708 + switch ($status) { 709 + case PhabricatorConfigStorageSchema::STATUS_NOTE: 710 + $icon = PHUIStatusItemView::ICON_INFO; 711 + $color = 'blue'; 712 + break; 713 + case PhabricatorConfigStorageSchema::STATUS_WARN: 714 + $icon = PHUIStatusItemView::ICON_WARNING; 715 + $color = 'yellow'; 716 + break; 717 + case PhabricatorConfigStorageSchema::STATUS_FAIL: 718 + default: 719 + $icon = PHUIStatusItemView::ICON_REJECT; 720 + $color = 'red'; 721 + break; 722 + } 723 + 724 + $item = id(new PHUIStatusItemView()) 725 + ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue)) 726 + ->setIcon($icon, $color) 727 + ->setNote($note); 728 + 729 + $status_view->addItem($item); 730 + } 731 + } 732 + $view->addProperty(pht('Schema Status'), $status_view); 733 + 734 + return $view; 735 + } 736 + 737 + }
+14
src/applications/config/schema/PhabricatorConfigKeySchema.php
··· 4 4 extends PhabricatorConfigStorageSchema { 5 5 6 6 private $columnNames; 7 + private $unique; 8 + 9 + public function setUnique($unique) { 10 + $this->unique = $unique; 11 + return $this; 12 + } 13 + 14 + public function getUnique() { 15 + return $this->unique; 16 + } 7 17 8 18 public function setColumnNames(array $column_names) { 9 19 $this->columnNames = array_values($column_names); ··· 24 34 $issues = array(); 25 35 if ($this->getColumnNames() !== $expect->getColumnNames()) { 26 36 $issues[] = self::ISSUE_KEYCOLUMNS; 37 + } 38 + 39 + if ($this->getUnique() !== $expect->getUnique()) { 40 + $issues[] = self::ISSUE_UNIQUE; 27 41 } 28 42 29 43 return $issues;
+13 -18
src/applications/config/schema/PhabricatorConfigSchemaQuery.php
··· 69 69 $column_info = array(); 70 70 } 71 71 72 - if ($sql) { 73 - $key_info = queryfx_all( 74 - $conn, 75 - 'SELECT CONSTRAINT_NAME, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, 76 - ORDINAL_POSITION, POSITION_IN_UNIQUE_CONSTRAINT 77 - FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 78 - WHERE (%Q)', 79 - '('.implode(') OR (', $sql).')'); 80 - $key_info = igroup($key_info, 'TABLE_SCHEMA'); 81 - } else { 82 - $key_info = array(); 83 - } 72 + // NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain 73 + // primary, unique, and foreign keys, so we can't use them here. We pull 74 + // indexes later on using SHOW INDEXES. 84 75 85 76 $server_schema = new PhabricatorConfigServerSchema(); 86 77 ··· 95 86 96 87 $database_column_info = idx($column_info, $database_name, array()); 97 88 $database_column_info = igroup($database_column_info, 'TABLE_NAME'); 98 - $database_key_info = idx($key_info, $database_name, array()); 99 - $database_key_info = igroup($database_key_info, 'TABLE_NAME'); 100 89 101 90 foreach ($database_tables as $table) { 102 91 $table_name = $table['TABLE_NAME']; ··· 117 106 $table_schema->addColumn($column_schema); 118 107 } 119 108 120 - $key_parts = idx($database_key_info, $table_name, array()); 121 - $keys = igroup($key_parts, 'CONSTRAINT_NAME'); 109 + $key_parts = queryfx_all( 110 + $conn, 111 + 'SHOW INDEXES FROM %T.%T', 112 + $database_name, 113 + $table_name); 114 + $keys = igroup($key_parts, 'Key_name'); 122 115 foreach ($keys as $key_name => $key_pieces) { 123 - $key_pieces = isort($key_pieces, 'ORDINAL_POSITION'); 116 + $key_pieces = isort($key_pieces, 'Seq_in_index'); 117 + $head = head($key_pieces); 124 118 $key_schema = id(new PhabricatorConfigKeySchema()) 125 119 ->setName($key_name) 126 - ->setColumnNames(ipull($key_pieces, 'COLUMN_NAME')); 120 + ->setColumnNames(ipull($key_pieces, 'Column_name')) 121 + ->setUnique(!$head['Non_unique']); 127 122 128 123 $table_schema->addKey($key_schema); 129 124 }
+2
src/applications/config/schema/PhabricatorConfigSchemaSpec.php
··· 100 100 $key = $this->newKey($key_name) 101 101 ->setColumnNames(idx($key_spec, 'columns', array())); 102 102 103 + $key->setUnique((bool)idx($key_spec, 'unique')); 104 + 103 105 $table->addKey($key); 104 106 } 105 107
+10
src/applications/config/schema/PhabricatorConfigStorageSchema.php
··· 9 9 const ISSUE_COLUMNTYPE = 'columntype'; 10 10 const ISSUE_NULLABLE = 'nullable'; 11 11 const ISSUE_KEYCOLUMNS = 'keycolumns'; 12 + const ISSUE_UNIQUE = 'unique'; 12 13 const ISSUE_SUBNOTE = 'subnote'; 13 14 const ISSUE_SUBWARN = 'subwarn'; 14 15 const ISSUE_SUBFAIL = 'subfail'; ··· 70 71 } 71 72 72 73 return $issues; 74 + } 75 + 76 + public function getLocalIssues() { 77 + return $this->issues; 73 78 } 74 79 75 80 public function hasIssue($issue) { ··· 109 114 return pht('Wrong Nullable Setting'); 110 115 case self::ISSUE_KEYCOLUMNS: 111 116 return pht('Key on Wrong Columns'); 117 + case self::ISSUE_UNIQUE: 118 + return pht('Key has Wrong Uniqueness'); 112 119 case self::ISSUE_SUBNOTE: 113 120 return pht('Subschemata Have Notices'); 114 121 case self::ISSUE_SUBWARN: ··· 136 143 return pht('This schema has the wrong nullable setting.'); 137 144 case self::ISSUE_KEYCOLUMNS: 138 145 return pht('This schema is on the wrong columns.'); 146 + case self::ISSUE_UNIQUE: 147 + return pht('This key has the wrong uniqueness setting.'); 139 148 case self::ISSUE_SUBNOTE: 140 149 return pht('Subschemata have setup notices.'); 141 150 case self::ISSUE_SUBWARN: ··· 157 166 case self::ISSUE_SUBWARN: 158 167 case self::ISSUE_KEYCOLUMNS: 159 168 case self::ISSUE_NULLABLE: 169 + case self::ISSUE_UNIQUE: 160 170 return self::STATUS_WARN; 161 171 case self::ISSUE_SUBNOTE: 162 172 case self::ISSUE_CHARSET:
+2
src/infrastructure/storage/lisk/LiskDAO.php
··· 1810 1810 case 'id': 1811 1811 $default_map['PRIMARY'] = array( 1812 1812 'columns' => array('id'), 1813 + 'unique' => true, 1813 1814 ); 1814 1815 break; 1815 1816 case 'phid': 1816 1817 $default_map['key_phid'] = array( 1817 1818 'columns' => array('phid'), 1819 + 'unique' => true, 1818 1820 ); 1819 1821 break; 1820 1822 }