@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<?php
2
3final class PhabricatorConfigDatabaseStatusController
4 extends PhabricatorConfigDatabaseController {
5
6 private $database;
7 private $table;
8 private $column;
9 private $key;
10 private $ref;
11
12 public function handleRequest(AphrontRequest $request) {
13 $viewer = $request->getViewer();
14 $this->database = $request->getURIData('database');
15 $this->table = $request->getURIData('table');
16 $this->column = $request->getURIData('column');
17 $this->key = $request->getURIData('key');
18 $this->ref = $request->getURIData('ref');
19
20 $query = new PhabricatorConfigSchemaQuery();
21
22 $actual = $query->loadActualSchemata();
23 $expect = $query->loadExpectedSchemata();
24 $comp = $query->buildComparisonSchemata($expect, $actual);
25
26 if ($this->ref !== null) {
27 $server_actual = idx($actual, $this->ref);
28 if (!$server_actual) {
29 return new Aphront404Response();
30 }
31
32 $server_comparison = $comp[$this->ref];
33 $server_expect = $expect[$this->ref];
34
35 if ($this->column) {
36 return $this->renderColumn(
37 $server_comparison,
38 $server_expect,
39 $server_actual,
40 $this->database,
41 $this->table,
42 $this->column);
43 } else if ($this->key) {
44 return $this->renderKey(
45 $server_comparison,
46 $server_expect,
47 $server_actual,
48 $this->database,
49 $this->table,
50 $this->key);
51 } else if ($this->table) {
52 return $this->renderTable(
53 $server_comparison,
54 $server_expect,
55 $server_actual,
56 $this->database,
57 $this->table);
58 } else if ($this->database) {
59 return $this->renderDatabase(
60 $server_comparison,
61 $server_expect,
62 $server_actual,
63 $this->database);
64 }
65 }
66
67 return $this->renderServers(
68 $comp,
69 $expect,
70 $actual);
71 }
72
73 private function buildResponse($title, $body) {
74 $nav = $this->newNavigation('schemata');
75
76 if (!$title) {
77 $title = pht('Database Status');
78 }
79
80 $ref = $this->ref;
81 $database = $this->database;
82 $table = $this->table;
83 $column = $this->column;
84 $key = $this->key;
85
86 $links = array();
87 $links[] = array(
88 pht('Database Status'),
89 'database/',
90 );
91
92 if ($database) {
93 $links[] = array(
94 $database,
95 "database/{$ref}/{$database}/",
96 );
97 }
98
99 if ($table) {
100 $links[] = array(
101 $table,
102 "database/{$ref}/{$database}/{$table}/",
103 );
104 }
105
106 if ($column) {
107 $links[] = array(
108 $column,
109 "database/{$ref}/{$database}/{$table}/col/{$column}/",
110 );
111 }
112
113 if ($key) {
114 $links[] = array(
115 $key,
116 "database/{$ref}/{$database}/{$table}/key/{$key}/",
117 );
118 }
119
120 $crumbs = $this->newCrumbs();
121
122 $last_key = last_key($links);
123 foreach ($links as $link_key => $link) {
124 list($name, $href) = $link;
125 if ($link_key == $last_key) {
126 $crumbs->addTextCrumb($name);
127 } else {
128 $crumbs->addTextCrumb($name, $this->getApplicationURI($href));
129 }
130 }
131
132 $doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments');
133 $button = id(new PHUIButtonView())
134 ->setTag('a')
135 ->setIcon('fa-book')
136 ->setHref($doc_link)
137 ->setText(pht('Documentation'));
138
139 $header = $this->buildHeaderView($title, $button);
140
141 $content = id(new PHUITwoColumnView())
142 ->setHeader($header)
143 ->setFooter($body);
144
145 return $this->newPage()
146 ->setTitle($title)
147 ->setCrumbs($crumbs)
148 ->setNavigation($nav)
149 ->appendChild($content);
150 }
151
152
153 private function renderServers(
154 array $comp_servers,
155 array $expect_servers,
156 array $actual_servers) {
157
158 $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
159 $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
160
161 $rows = array();
162 foreach ($comp_servers as $ref_key => $comp) {
163 $actual = $actual_servers[$ref_key];
164 $expect = $expect_servers[$ref_key];
165 foreach ($comp->getDatabases() as $database_name => $database) {
166 $actual_database = $actual->getDatabase($database_name);
167 if ($actual_database) {
168 $charset = $actual_database->getCharacterSet();
169 $collation = $actual_database->getCollation();
170 } else {
171 $charset = null;
172 $collation = null;
173 }
174
175 $status = $database->getStatus();
176 $issues = $database->getIssues();
177
178 $uri = $this->getURI(
179 array(
180 'ref' => $ref_key,
181 'database' => $database_name,
182 ));
183
184 $rows[] = array(
185 $this->renderIcon($status),
186 $ref_key,
187 phutil_tag(
188 'a',
189 array(
190 'href' => $uri,
191 ),
192 $database_name),
193 $this->renderAttr($charset, $database->hasIssue($charset_issue)),
194 $this->renderAttr($collation, $database->hasIssue($collation_issue)),
195 );
196 }
197 }
198
199 $table = id(new AphrontTableView($rows))
200 ->setHeaders(
201 array(
202 null,
203 pht('Server'),
204 pht('Database'),
205 pht('Charset'),
206 pht('Collation'),
207 ))
208 ->setColumnClasses(
209 array(
210 null,
211 null,
212 'wide pri',
213 null,
214 null,
215 ));
216
217 $title = pht('Database Status');
218 $properties = $this->buildProperties(
219 array(
220 ),
221 $comp->getIssues());
222 $properties = $this->buildConfigBoxView(pht('Properties'), $properties);
223 $table = $this->buildConfigBoxView(pht('Database'), $table);
224
225 return $this->buildResponse($title, array($properties, $table));
226 }
227
228 private function renderDatabase(
229 PhabricatorConfigServerSchema $comp,
230 PhabricatorConfigServerSchema $expect,
231 PhabricatorConfigServerSchema $actual,
232 $database_name) {
233
234 $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
235
236 $database = $comp->getDatabase($database_name);
237 if (!$database) {
238 return new Aphront404Response();
239 }
240
241 $rows = array();
242 foreach ($database->getTables() as $table_name => $table) {
243 $status = $table->getStatus();
244
245 $uri = $this->getURI(
246 array(
247 'table' => $table_name,
248 ));
249
250 $rows[] = array(
251 $this->renderIcon($status),
252 phutil_tag(
253 'a',
254 array(
255 'href' => $uri,
256 ),
257 $table_name),
258 $this->renderAttr(
259 $table->getCollation(),
260 $table->hasIssue($collation_issue)),
261 $table->getPersistenceTypeDisplayName(),
262 );
263 }
264
265 $table = id(new AphrontTableView($rows))
266 ->setHeaders(
267 array(
268 null,
269 pht('Table'),
270 pht('Collation'),
271 pht('Persistence'),
272 ))
273 ->setColumnClasses(
274 array(
275 null,
276 'wide pri',
277 null,
278 null,
279 ));
280
281 $title = $database_name;
282
283 $actual_database = $actual->getDatabase($database_name);
284 if ($actual_database) {
285 $actual_charset = $actual_database->getCharacterSet();
286 $actual_collation = $actual_database->getCollation();
287 } else {
288 $actual_charset = null;
289 $actual_collation = null;
290 }
291
292 $expect_database = $expect->getDatabase($database_name);
293 if ($expect_database) {
294 $expect_charset = $expect_database->getCharacterSet();
295 $expect_collation = $expect_database->getCollation();
296 } else {
297 $expect_charset = null;
298 $expect_collation = null;
299 }
300
301 $properties = $this->buildProperties(
302 array(
303 array(
304 pht('Server'),
305 $this->ref,
306 ),
307 array(
308 pht('Character Set'),
309 $actual_charset,
310 ),
311 array(
312 pht('Expected Character Set'),
313 $expect_charset,
314 ),
315 array(
316 pht('Collation'),
317 $actual_collation,
318 ),
319 array(
320 pht('Expected Collation'),
321 $expect_collation,
322 ),
323 ),
324 $database->getIssues());
325
326 $properties = $this->buildConfigBoxView(pht('Properties'), $properties);
327 $table = $this->buildConfigBoxView(pht('Database'), $table);
328
329 return $this->buildResponse($title, array($properties, $table));
330 }
331
332 private function renderTable(
333 PhabricatorConfigServerSchema $comp,
334 PhabricatorConfigServerSchema $expect,
335 PhabricatorConfigServerSchema $actual,
336 $database_name,
337 $table_name) {
338
339 $type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE;
340 $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
341 $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
342 $nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE;
343 $unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE;
344 $columns_issue = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS;
345 $longkey_issue = PhabricatorConfigStorageSchema::ISSUE_LONGKEY;
346 $auto_issue = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT;
347
348 $database = $comp->getDatabase($database_name);
349 if (!$database) {
350 return new Aphront404Response();
351 }
352
353 $table = $database->getTable($table_name);
354 if (!$table) {
355 return new Aphront404Response();
356 }
357
358 $actual_database = $actual->getDatabase($database_name);
359 $actual_table = null;
360 if ($actual_database) {
361 $actual_table = $actual_database->getTable($table_name);
362 }
363
364 $expect_database = $expect->getDatabase($database_name);
365 $expect_table = null;
366 if ($expect_database) {
367 $expect_table = $expect_database->getTable($table_name);
368 }
369
370 $rows = array();
371 foreach ($table->getColumns() as $column_name => $column) {
372 $expect_column = null;
373 if ($expect_table) {
374 $expect_column = $expect_table->getColumn($column_name);
375 }
376
377 $status = $column->getStatus();
378
379 $data_type = null;
380 if ($expect_column) {
381 $data_type = $expect_column->getDataType();
382 }
383
384 $uri = $this->getURI(
385 array(
386 'column' => $column_name,
387 ));
388
389 $rows[] = array(
390 $this->renderIcon($status),
391 phutil_tag(
392 'a',
393 array(
394 'href' => $uri,
395 ),
396 $column_name),
397 $data_type,
398 $this->renderAttr(
399 $column->getColumnType(),
400 $column->hasIssue($type_issue)),
401 $this->renderAttr(
402 $this->renderBoolean($column->getNullable()),
403 $column->hasIssue($nullable_issue)),
404 $this->renderAttr(
405 $this->renderBoolean($column->getAutoIncrement()),
406 $column->hasIssue($auto_issue)),
407 $this->renderAttr(
408 $column->getCharacterSet(),
409 $column->hasIssue($charset_issue)),
410 $this->renderAttr(
411 $column->getCollation(),
412 $column->hasIssue($collation_issue)),
413 );
414 }
415
416 $table_view = id(new AphrontTableView($rows))
417 ->setHeaders(
418 array(
419 null,
420 pht('Column'),
421 pht('Data Type'),
422 pht('Column Type'),
423 pht('Nullable'),
424 pht('Autoincrement'),
425 pht('Character Set'),
426 pht('Collation'),
427 ))
428 ->setColumnClasses(
429 array(
430 null,
431 'wide pri',
432 null,
433 null,
434 null,
435 null,
436 null,
437 ));
438
439 $key_rows = array();
440 foreach ($table->getKeys() as $key_name => $key) {
441 $expect_key = null;
442 if ($expect_table) {
443 $expect_key = $expect_table->getKey($key_name);
444 }
445
446 $status = $key->getStatus();
447
448 $size = 0;
449 foreach ($key->getColumnNames() as $column_spec) {
450 list($column_name, $prefix) = $key->getKeyColumnAndPrefix($column_spec);
451 $column = $table->getColumn($column_name);
452 if (!$column) {
453 $size = 0;
454 break;
455 }
456 $size += $column->getKeyByteLength($prefix);
457 }
458
459 $size_formatted = null;
460 if ($size) {
461 $size_formatted = $this->renderAttr(
462 $size,
463 $key->hasIssue($longkey_issue));
464 }
465
466 $uri = $this->getURI(
467 array(
468 'key' => $key_name,
469 ));
470
471 $key_rows[] = array(
472 $this->renderIcon($status),
473 phutil_tag(
474 'a',
475 array(
476 'href' => $uri,
477 ),
478 $key_name),
479 $this->renderAttr(
480 implode(', ', $key->getColumnNames()),
481 $key->hasIssue($columns_issue)),
482 $this->renderAttr(
483 $this->renderBoolean($key->getUnique()),
484 $key->hasIssue($unique_issue)),
485 $size_formatted,
486 );
487 }
488
489 $keys_view = id(new AphrontTableView($key_rows))
490 ->setHeaders(
491 array(
492 null,
493 pht('Key'),
494 pht('Columns'),
495 pht('Unique'),
496 pht('Size'),
497 ))
498 ->setColumnClasses(
499 array(
500 null,
501 'wide pri',
502 null,
503 null,
504 null,
505 ));
506
507 $title = pht('%s.%s', $database_name, $table_name);
508
509 if ($actual_table) {
510 $actual_collation = $actual_table->getCollation();
511 } else {
512 $actual_collation = null;
513 }
514
515 if ($expect_table) {
516 $expect_collation = $expect_table->getCollation();
517 } else {
518 $expect_collation = null;
519 }
520
521 $properties = $this->buildProperties(
522 array(
523 array(
524 pht('Server'),
525 $this->ref,
526 ),
527 array(
528 pht('Collation'),
529 $actual_collation,
530 ),
531 array(
532 pht('Expected Collation'),
533 $expect_collation,
534 ),
535 ),
536 $table->getIssues());
537
538 $box_header = pht('%s.%s', $database_name, $table_name);
539
540 $properties = $this->buildConfigBoxView(pht('Properties'), $properties);
541 $table = $this->buildConfigBoxView(pht('Database'), $table_view);
542 $keys = $this->buildConfigBoxView(pht('Keys'), $keys_view);
543
544 return $this->buildResponse(
545 $title, array($properties, $table, $keys));
546 }
547
548 private function renderColumn(
549 PhabricatorConfigServerSchema $comp,
550 PhabricatorConfigServerSchema $expect,
551 PhabricatorConfigServerSchema $actual,
552 $database_name,
553 $table_name,
554 $column_name) {
555
556 $database = $comp->getDatabase($database_name);
557 if (!$database) {
558 return new Aphront404Response();
559 }
560
561 $table = $database->getTable($table_name);
562 if (!$table) {
563 return new Aphront404Response();
564 }
565
566 $column = $table->getColumn($column_name);
567 if (!$column) {
568 return new Aphront404Response();
569 }
570
571 $actual_database = $actual->getDatabase($database_name);
572 $actual_table = null;
573 $actual_column = null;
574 if ($actual_database) {
575 $actual_table = $actual_database->getTable($table_name);
576 if ($actual_table) {
577 $actual_column = $actual_table->getColumn($column_name);
578 }
579 }
580
581 $expect_database = $expect->getDatabase($database_name);
582 $expect_table = null;
583 $expect_column = null;
584 if ($expect_database) {
585 $expect_table = $expect_database->getTable($table_name);
586 if ($expect_table) {
587 $expect_column = $expect_table->getColumn($column_name);
588 }
589 }
590
591 if ($actual_column) {
592 $actual_coltype = $actual_column->getColumnType();
593 $actual_charset = $actual_column->getCharacterSet();
594 $actual_collation = $actual_column->getCollation();
595 $actual_nullable = $actual_column->getNullable();
596 $actual_auto = $actual_column->getAutoIncrement();
597 } else {
598 $actual_coltype = null;
599 $actual_charset = null;
600 $actual_collation = null;
601 $actual_nullable = null;
602 $actual_auto = null;
603 }
604
605 if ($expect_column) {
606 $data_type = $expect_column->getDataType();
607 $expect_coltype = $expect_column->getColumnType();
608 $expect_charset = $expect_column->getCharacterSet();
609 $expect_collation = $expect_column->getCollation();
610 $expect_nullable = $expect_column->getNullable();
611 $expect_auto = $expect_column->getAutoIncrement();
612 } else {
613 $data_type = null;
614 $expect_coltype = null;
615 $expect_charset = null;
616 $expect_collation = null;
617 $expect_nullable = null;
618 $expect_auto = null;
619 }
620
621
622 $title = pht(
623 '%s.%s.%s',
624 $database_name,
625 $table_name,
626 $column_name);
627
628 $properties = $this->buildProperties(
629 array(
630 array(
631 pht('Server'),
632 $this->ref,
633 ),
634 array(
635 pht('Data Type'),
636 $data_type,
637 ),
638 array(
639 pht('Column Type'),
640 $actual_coltype,
641 ),
642 array(
643 pht('Expected Column Type'),
644 $expect_coltype,
645 ),
646 array(
647 pht('Character Set'),
648 $actual_charset,
649 ),
650 array(
651 pht('Expected Character Set'),
652 $expect_charset,
653 ),
654 array(
655 pht('Collation'),
656 $actual_collation,
657 ),
658 array(
659 pht('Expected Collation'),
660 $expect_collation,
661 ),
662 array(
663 pht('Nullable'),
664 $this->renderBoolean($actual_nullable),
665 ),
666 array(
667 pht('Expected Nullable'),
668 $this->renderBoolean($expect_nullable),
669 ),
670 array(
671 pht('Autoincrement'),
672 $this->renderBoolean($actual_auto),
673 ),
674 array(
675 pht('Expected Autoincrement'),
676 $this->renderBoolean($expect_auto),
677 ),
678 ),
679 $column->getIssues());
680
681 $properties = $this->buildConfigBoxView(pht('Properties'), $properties);
682
683 return $this->buildResponse($title, $properties);
684 }
685
686 private function renderKey(
687 PhabricatorConfigServerSchema $comp,
688 PhabricatorConfigServerSchema $expect,
689 PhabricatorConfigServerSchema $actual,
690 $database_name,
691 $table_name,
692 $key_name) {
693
694 $database = $comp->getDatabase($database_name);
695 if (!$database) {
696 return new Aphront404Response();
697 }
698
699 $table = $database->getTable($table_name);
700 if (!$table) {
701 return new Aphront404Response();
702 }
703
704 $key = $table->getKey($key_name);
705 if (!$key) {
706 return new Aphront404Response();
707 }
708
709 $actual_database = $actual->getDatabase($database_name);
710 $actual_table = null;
711 $actual_key = null;
712 if ($actual_database) {
713 $actual_table = $actual_database->getTable($table_name);
714 if ($actual_table) {
715 $actual_key = $actual_table->getKey($key_name);
716 }
717 }
718
719 $expect_database = $expect->getDatabase($database_name);
720 $expect_table = null;
721 $expect_key = null;
722 if ($expect_database) {
723 $expect_table = $expect_database->getTable($table_name);
724 if ($expect_table) {
725 $expect_key = $expect_table->getKey($key_name);
726 }
727 }
728
729 if ($actual_key) {
730 $actual_columns = $actual_key->getColumnNames();
731 $actual_unique = $actual_key->getUnique();
732 } else {
733 $actual_columns = array();
734 $actual_unique = null;
735 }
736
737 if ($expect_key) {
738 $expect_columns = $expect_key->getColumnNames();
739 $expect_unique = $expect_key->getUnique();
740 } else {
741 $expect_columns = array();
742 $expect_unique = null;
743 }
744
745 $title = pht(
746 '%s.%s (%s)',
747 $database_name,
748 $table_name,
749 $key_name);
750
751 $properties = $this->buildProperties(
752 array(
753 array(
754 pht('Server'),
755 $this->ref,
756 ),
757 array(
758 pht('Unique'),
759 $this->renderBoolean($actual_unique),
760 ),
761 array(
762 pht('Expected Unique'),
763 $this->renderBoolean($expect_unique),
764 ),
765 array(
766 pht('Columns'),
767 implode(', ', $actual_columns),
768 ),
769 array(
770 pht('Expected Columns'),
771 implode(', ', $expect_columns),
772 ),
773 ),
774 $key->getIssues());
775
776 $properties = $this->buildConfigBoxView(pht('Properties'), $properties);
777
778 return $this->buildResponse($title, $properties);
779 }
780
781 private function buildProperties(array $properties, array $issues) {
782 $view = id(new PHUIPropertyListView())
783 ->setUser($this->getRequest()->getUser());
784
785 foreach ($properties as $property) {
786 list($key, $value) = $property;
787 $view->addProperty($key, $value);
788 }
789
790 $status_view = new PHUIStatusListView();
791 if (!$issues) {
792 $status_view->addItem(
793 id(new PHUIStatusItemView())
794 ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
795 ->setTarget(pht('No Schema Issues')));
796 } else {
797 foreach ($issues as $issue) {
798 $note = PhabricatorConfigStorageSchema::getIssueDescription($issue);
799
800 $status = PhabricatorConfigStorageSchema::getIssueStatus($issue);
801 switch ($status) {
802 case PhabricatorConfigStorageSchema::STATUS_WARN:
803 $icon = PHUIStatusItemView::ICON_WARNING;
804 $color = 'yellow';
805 break;
806 case PhabricatorConfigStorageSchema::STATUS_FAIL:
807 default:
808 $icon = PHUIStatusItemView::ICON_REJECT;
809 $color = 'red';
810 break;
811 }
812
813 $item = id(new PHUIStatusItemView())
814 ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue))
815 ->setIcon($icon, $color)
816 ->setNote($note);
817
818 $status_view->addItem($item);
819 }
820 }
821 $view->addProperty(pht('Schema Status'), $status_view);
822
823 return phutil_tag_div('config-page-property', $view);
824 }
825
826 private function getURI(array $properties) {
827 $defaults = array(
828 'ref' => $this->ref,
829 'database' => $this->database,
830 'table' => $this->table,
831 'column' => $this->column,
832 'key' => $this->key,
833 );
834
835 $properties = $properties + $defaults;
836 $properties = array_select_keys($properties, array_keys($defaults));
837
838 $parts = array();
839 foreach ($properties as $key => $property) {
840 if (!phutil_nonempty_string($property)) {
841 continue;
842 }
843
844 if ($key == 'column') {
845 $parts[] = 'col';
846 } else if ($key == 'key') {
847 $parts[] = 'key';
848 }
849
850 $parts[] = $property;
851 }
852
853 if ($parts) {
854 $parts = implode('/', $parts).'/';
855 } else {
856 $parts = null;
857 }
858
859 return $this->getApplicationURI('/database/'.$parts);
860 }
861
862}