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

Generate expected and comparison schemata

Summary:
Ref T1191. This builds on the "view of the database as it exists" by building a view of the database as it is expected to exist (this is mostly empty for now) and comparing the two. We now render a view of the "comparison schema", which is the actual schema merged with the expected schema and annotated with the differences.

(I'm merging them like this because it makes it easier to handle both "missing" and "surpulus" warnings in a consistent way. If we tried to annotate just the actual or expected schema, the absence of components which are expected to exist is messy to handle.)

Test Plan: See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1191

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

+605 -92
+8 -4
src/__phutil_library_map__.php
··· 1360 1360 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 1361 1361 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 1362 1362 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 1363 + 'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php', 1363 1364 'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php', 1364 1365 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', 1365 1366 'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php', 1367 + 'PhabricatorConfigStorageSchema' => 'applications/config/schema/PhabricatorConfigStorageSchema.php', 1366 1368 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 1367 1369 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 1368 1370 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', ··· 4216 4218 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 4217 4219 'PhabricatorConfigAllController' => 'PhabricatorConfigController', 4218 4220 'PhabricatorConfigApplication' => 'PhabricatorApplication', 4219 - 'PhabricatorConfigColumnSchema' => 'Phobject', 4221 + 'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema', 4220 4222 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 4221 4223 'PhabricatorConfigController' => 'PhabricatorController', 4222 4224 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', 4223 - 'PhabricatorConfigDatabaseSchema' => 'Phobject', 4225 + 'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema', 4224 4226 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', 4225 4227 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 4226 4228 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', ··· 4252 4254 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 4253 4255 'PhabricatorConfigResponse' => 'AphrontHTMLResponse', 4254 4256 'PhabricatorConfigSchemaQuery' => 'Phobject', 4255 - 'PhabricatorConfigServerSchema' => 'Phobject', 4257 + 'PhabricatorConfigSchemaSpec' => 'Phobject', 4258 + 'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema', 4256 4259 'PhabricatorConfigStackSource' => 'PhabricatorConfigSource', 4257 - 'PhabricatorConfigTableSchema' => 'Phobject', 4260 + 'PhabricatorConfigStorageSchema' => 'Phobject', 4261 + 'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema', 4258 4262 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 4259 4263 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4260 4264 'PhabricatorConfigValidationException' => 'Exception',
+4 -1
src/applications/config/application/PhabricatorConfigApplication.php
··· 42 42 'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController', 43 43 'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController', 44 44 'welcome/' => 'PhabricatorConfigWelcomeController', 45 - 'database/(?:(?P<database>[^/]+)/(?:(?P<table>[^/]+)/)?)?' 45 + 'database/'. 46 + '(?:(?P<database>[^/]+)/'. 47 + '(?:(?P<table>[^/]+)/'. 48 + '(?:(?P<column>[^/]+)/)?)?)?' 46 49 => 'PhabricatorConfigDatabaseController', 47 50 '(?P<verb>ignore|unignore)/(?P<key>[^/]+)/' 48 51 => 'PhabricatorConfigIgnoreController',
+235 -46
src/applications/config/controller/PhabricatorConfigDatabaseController.php
··· 5 5 6 6 private $database; 7 7 private $table; 8 + private $column; 8 9 9 10 public function willProcessRequest(array $data) { 10 11 $this->database = idx($data, 'database'); 11 12 $this->table = idx($data, 'table'); 13 + $this->column = idx($data, 'column'); 12 14 } 13 15 14 16 public function processRequest() { ··· 31 33 32 34 $actual = $query->loadActualSchema(); 33 35 $expect = $query->loadExpectedSchema(); 36 + $comp = $query->buildComparisonSchema($expect, $actual); 34 37 35 - if ($this->table) { 36 - return $this->renderTable( 38 + if ($this->column) { 39 + return $this->renderColumn( 40 + $comp, 41 + $expect, 37 42 $actual, 43 + $this->database, 44 + $this->table, 45 + $this->column); 46 + } else if ($this->table) { 47 + return $this->renderTable( 48 + $comp, 38 49 $expect, 50 + $actual, 39 51 $this->database, 40 52 $this->table); 41 53 } else if ($this->database) { 42 54 return $this->renderDatabase( 43 - $actual, 55 + $comp, 44 56 $expect, 57 + $actual, 45 58 $this->database); 46 59 } else { 47 60 return $this->renderServer( 48 - $actual, 49 - $expect); 61 + $comp, 62 + $expect, 63 + $actual); 50 64 } 51 65 } 52 66 ··· 83 97 84 98 85 99 private function renderServer( 86 - PhabricatorConfigServerSchema $schema, 87 - PhabricatorConfigServerSchema $expect) { 88 - 89 - $icon_ok = id(new PHUIIconView()) 90 - ->setIconFont('fa-check-circle green'); 100 + PhabricatorConfigServerSchema $comp, 101 + PhabricatorConfigServerSchema $expect, 102 + PhabricatorConfigServerSchema $actual) { 91 103 92 - $icon_warn = id(new PHUIIconView()) 93 - ->setIconFont('fa-exclamation-circle yellow'); 104 + $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET; 105 + $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION; 94 106 95 107 $rows = array(); 96 - foreach ($schema->getDatabases() as $database_name => $database) { 97 - 98 - $expect_database = $expect->getDatabase($database_name); 99 - if ($expect_database) { 100 - $expect_set = $expect_database->getCharacterSet(); 101 - $expect_collation = $expect_database->getCollation(); 102 - 103 - if ($database->isSameSchema($expect_database)) { 104 - $icon = $icon_ok; 105 - } else { 106 - $icon = $icon_warn; 107 - } 108 + foreach ($comp->getDatabases() as $database_name => $database) { 109 + $actual_database = $actual->getDatabase($database_name); 110 + if ($actual_database) { 111 + $charset = $actual_database->getCharacterSet(); 112 + $collation = $actual_database->getCollation(); 108 113 } else { 109 - $expect_set = null; 110 - $expect_collation = null; 111 - $icon = $icon_warn; 114 + $charset = null; 115 + $collation = null; 112 116 } 113 117 114 - $actual_set = $database->getCharacterSet(); 115 - $actual_collation = $database->getCollation(); 116 - 117 - 118 + $status = $database->getStatus(); 119 + $issues = $database->getIssues(); 118 120 119 121 $rows[] = array( 120 - $icon, 122 + $this->renderIcon($status), 121 123 phutil_tag( 122 124 'a', 123 125 array( ··· 125 127 '/database/'.$database_name.'/'), 126 128 ), 127 129 $database_name), 128 - $actual_set, 129 - $expect_set, 130 - $actual_collation, 131 - $expect_collation, 130 + $this->renderAttr($charset, $database->hasIssue($charset_issue)), 131 + $this->renderAttr($collation, $database->hasIssue($collation_issue)), 132 132 ); 133 133 } 134 134 ··· 138 138 null, 139 139 pht('Database'), 140 140 pht('Charset'), 141 - pht('Expected Charset'), 142 141 pht('Collation'), 143 - pht('Expected Collation'), 144 142 )) 145 143 ->setColumnClasses( 146 144 array( 147 - '', 145 + null, 148 146 'wide pri', 149 147 null, 150 148 null, ··· 152 150 153 151 $title = pht('Database Status'); 154 152 153 + $properties = $this->buildProperties( 154 + array( 155 + ), 156 + $comp->getIssues()); 157 + 155 158 $box = id(new PHUIObjectBoxView()) 156 159 ->setHeaderText($title) 160 + ->addPropertyList($properties) 157 161 ->appendChild($table); 158 162 159 163 return $this->buildResponse($title, $box); 160 164 } 161 165 162 166 private function renderDatabase( 163 - PhabricatorConfigServerSchema $schema, 167 + PhabricatorConfigServerSchema $comp, 164 168 PhabricatorConfigServerSchema $expect, 169 + PhabricatorConfigServerSchema $actual, 165 170 $database_name) { 166 171 167 - $database = $schema->getDatabase($database_name); 172 + $database = $comp->getDatabase($database_name); 168 173 if (!$database) { 169 174 return new Aphront404Response(); 170 175 } 171 176 172 177 $rows = array(); 173 178 foreach ($database->getTables() as $table_name => $table) { 179 + 180 + $status = $table->getStatus(); 181 + $issues = $table->getIssues(); 182 + 174 183 $rows[] = array( 184 + $this->renderIcon($status), 175 185 phutil_tag( 176 186 'a', 177 187 array( ··· 186 196 $table = id(new AphrontTableView($rows)) 187 197 ->setHeaders( 188 198 array( 199 + null, 189 200 pht('Table'), 190 201 pht('Collation'), 191 202 )) 192 203 ->setColumnClasses( 193 204 array( 205 + null, 194 206 'wide pri', 195 207 null, 196 208 )); 197 209 198 210 $title = pht('Database Status: %s', $database_name); 199 211 212 + $actual_database = $actual->getDatabase($database_name); 213 + if ($actual_database) { 214 + $actual_charset = $actual_database->getCharacterSet(); 215 + $actual_collation = $actual_database->getCollation(); 216 + } else { 217 + $actual_charset = null; 218 + $actual_collation = null; 219 + } 220 + 221 + $expect_database = $expect->getDatabase($database_name); 222 + if ($expect_database) { 223 + $expect_charset = $expect_database->getCharacterSet(); 224 + $expect_collation = $expect_database->getCollation(); 225 + } else { 226 + $expect_charset = null; 227 + $expect_collation = null; 228 + } 229 + 230 + $properties = $this->buildProperties( 231 + array( 232 + array( 233 + pht('Character Set'), 234 + $actual_charset, 235 + ), 236 + array( 237 + pht('Expected Character Set'), 238 + $expect_charset, 239 + ), 240 + array( 241 + pht('Collation'), 242 + $actual_collation, 243 + ), 244 + array( 245 + pht('Expected Collation'), 246 + $expect_collation, 247 + ), 248 + ), 249 + $database->getIssues()); 250 + 200 251 $box = id(new PHUIObjectBoxView()) 201 252 ->setHeaderText($title) 253 + ->addPropertyList($properties) 202 254 ->appendChild($table); 203 255 204 256 return $this->buildResponse($title, $box); 205 257 } 206 258 207 259 private function renderTable( 208 - PhabricatorConfigServerSchema $schema, 260 + PhabricatorConfigServerSchema $comp, 209 261 PhabricatorConfigServerSchema $expect, 262 + PhabricatorConfigServerSchema $actual, 210 263 $database_name, 211 264 $table_name) { 212 265 213 - $database = $schema->getDatabase($database_name); 266 + $database = $comp->getDatabase($database_name); 214 267 if (!$database) { 215 268 return new Aphront404Response(); 216 269 } ··· 222 275 223 276 $rows = array(); 224 277 foreach ($table->getColumns() as $column_name => $column) { 278 + $status = $column->getStatus(); 279 + 225 280 $rows[] = array( 226 - $column_name, 281 + $this->renderIcon($status), 282 + phutil_tag( 283 + 'a', 284 + array( 285 + 'href' => $this->getApplicationURI( 286 + 'database/'. 287 + $database_name.'/'. 288 + $table_name.'/'. 289 + $column_name.'/'), 290 + ), 291 + $column_name), 227 292 $column->getColumnType(), 228 293 $column->getCharacterSet(), 229 294 $column->getCollation(), 230 295 ); 231 296 } 232 297 233 - $table = id(new AphrontTableView($rows)) 298 + $table_view = id(new AphrontTableView($rows)) 234 299 ->setHeaders( 235 300 array( 301 + null, 236 302 pht('Table'), 237 303 pht('Column Type'), 238 304 pht('Character Set'), ··· 240 306 )) 241 307 ->setColumnClasses( 242 308 array( 309 + null, 243 310 'wide pri', 244 311 null, 245 312 null, ··· 248 315 249 316 $title = pht('Database Status: %s.%s', $database_name, $table_name); 250 317 318 + $properties = $this->buildProperties( 319 + array( 320 + ), 321 + $table->getIssues()); 322 + 251 323 $box = id(new PHUIObjectBoxView()) 252 324 ->setHeaderText($title) 253 - ->appendChild($table); 325 + ->addPropertyList($properties) 326 + ->appendChild($table_view); 254 327 255 328 return $this->buildResponse($title, $box); 329 + } 330 + 331 + private function renderColumn( 332 + PhabricatorConfigServerSchema $comp, 333 + PhabricatorConfigServerSchema $expect, 334 + PhabricatorConfigServerSchema $actual, 335 + $database_name, 336 + $table_name, 337 + $column_name) { 338 + 339 + $database = $comp->getDatabase($database_name); 340 + if (!$database) { 341 + return new Aphront404Response(); 342 + } 343 + 344 + $table = $database->getTable($table_name); 345 + if (!$table) { 346 + return new Aphront404Response(); 347 + } 348 + 349 + $column = $table->getColumn($column_name); 350 + if (!$table) { 351 + return new Aphront404Response(); 352 + } 353 + 354 + $title = pht( 355 + 'Database Status: %s.%s.%s', 356 + $database_name, 357 + $table_name, 358 + $column_name); 359 + 360 + $properties = $this->buildProperties( 361 + array( 362 + ), 363 + $column->getIssues()); 364 + 365 + $box = id(new PHUIObjectBoxView()) 366 + ->setHeaderText($title) 367 + ->addPropertyList($properties); 368 + 369 + return $this->buildResponse($title, $box); 370 + } 371 + private function renderIcon($status) { 372 + switch ($status) { 373 + case PhabricatorConfigStorageSchema::STATUS_OKAY: 374 + $icon = 'fa-check-circle green'; 375 + break; 376 + case PhabricatorConfigStorageSchema::STATUS_WARN: 377 + $icon = 'fa-exclamation-circle yellow'; 378 + break; 379 + case PhabricatorConfigStorageSchema::STATUS_FAIL: 380 + default: 381 + $icon = 'fa-times-circle red'; 382 + break; 383 + } 384 + 385 + return id(new PHUIIconView()) 386 + ->setIconFont($icon); 387 + } 388 + 389 + private function renderAttr($attr, $issue) { 390 + if ($issue) { 391 + return phutil_tag( 392 + 'span', 393 + array( 394 + 'style' => 'color: #aa0000;', 395 + ), 396 + $attr); 397 + } else { 398 + return $attr; 399 + } 400 + } 401 + 402 + private function buildProperties(array $properties, array $issues) { 403 + $view = id(new PHUIPropertyListView()) 404 + ->setUser($this->getRequest()->getUser()); 405 + 406 + foreach ($properties as $property) { 407 + list($key, $value) = $property; 408 + $view->addProperty($key, $value); 409 + } 410 + 411 + $status_view = new PHUIStatusListView(); 412 + if (!$issues) { 413 + $status_view->addItem( 414 + id(new PHUIStatusItemView()) 415 + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') 416 + ->setTarget(pht('No Schema Issues'))); 417 + } else { 418 + foreach ($issues as $issue) { 419 + $note = PhabricatorConfigStorageSchema::getIssueDescription($issue); 420 + 421 + $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); 422 + switch ($status) { 423 + case PhabricatorConfigStorageSchema::STATUS_WARN: 424 + $icon = PHUIStatusItemView::ICON_WARNING; 425 + $color = 'yellow'; 426 + break; 427 + case PhabricatorConfigStorageSchema::STATUS_FAIL: 428 + default: 429 + $icon = PHUIStatusItemView::ICON_REJECT; 430 + $color = 'red'; 431 + break; 432 + } 433 + 434 + $item = id(new PHUIStatusItemView()) 435 + ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue)) 436 + ->setIcon($icon, $color) 437 + ->setNote($note); 438 + 439 + $status_view->addItem($item); 440 + } 441 + } 442 + $view->addProperty(pht('Schema Status'), $status_view); 443 + 444 + return $view; 256 445 } 257 446 258 447 }
+26 -7
src/applications/config/schema/PhabricatorConfigColumnSchema.php
··· 1 1 <?php 2 2 3 - final class PhabricatorConfigColumnSchema extends Phobject { 3 + final class PhabricatorConfigColumnSchema 4 + extends PhabricatorConfigStorageSchema { 4 5 5 - private $name; 6 6 private $characterSet; 7 7 private $collation; 8 8 private $columnType; ··· 14 14 15 15 public function getColumnType() { 16 16 return $this->columnType; 17 + } 18 + 19 + protected function getSubschemata() { 20 + return array(); 17 21 } 18 22 19 23 public function setCollation($collation) { ··· 34 38 return $this->characterSet; 35 39 } 36 40 37 - public function setName($name) { 38 - $this->name = $name; 39 - return $this; 41 + public function compareToSimilarSchema( 42 + PhabricatorConfigStorageSchema $expect) { 43 + 44 + $issues = array(); 45 + if ($this->getCharacterSet() != $expect->getCharacterSet()) { 46 + $issues[] = self::ISSUE_CHARSET; 47 + } 48 + 49 + if ($this->getCollation() != $expect->getCollation()) { 50 + $issues[] = self::ISSUE_COLLATION; 51 + } 52 + 53 + if ($this->getColumnType() != $expect->getColumnType()) { 54 + $issues[] = self::ISSUE_COLUMNTYPE; 55 + } 56 + 57 + return $issues; 40 58 } 41 59 42 - public function getName() { 43 - return $this->name; 60 + public function newEmptyClone() { 61 + $clone = clone $this; 62 + return $clone; 44 63 } 45 64 46 65 }
+23 -19
src/applications/config/schema/PhabricatorConfigDatabaseSchema.php
··· 1 1 <?php 2 2 3 - final class PhabricatorConfigDatabaseSchema extends Phobject { 3 + final class PhabricatorConfigDatabaseSchema 4 + extends PhabricatorConfigStorageSchema { 4 5 5 - private $name; 6 6 private $characterSet; 7 7 private $collation; 8 8 private $tables = array(); ··· 25 25 return idx($this->tables, $key); 26 26 } 27 27 28 - public function isSameSchema(PhabricatorConfigDatabaseSchema $expect) { 29 - return ($this->toDictionary() === $expect->toDictionary()); 28 + protected function getSubschemata() { 29 + return $this->getTables(); 30 30 } 31 31 32 - public function toDictionary() { 33 - return array( 34 - 'name' => $this->getName(), 35 - 'characterSet' => $this->getCharacterSet(), 36 - 'collation' => $this->getCollation(), 37 - ); 32 + public function compareToSimilarSchema( 33 + PhabricatorConfigStorageSchema $expect) { 34 + 35 + $issues = array(); 36 + if ($this->getCharacterSet() != $expect->getCharacterSet()) { 37 + $issues[] = self::ISSUE_CHARSET; 38 + } 39 + 40 + if ($this->getCollation() != $expect->getCollation()) { 41 + $issues[] = self::ISSUE_COLLATION; 42 + } 43 + 44 + return $issues; 45 + } 46 + 47 + public function newEmptyClone() { 48 + $clone = clone $this; 49 + $clone->tables = array(); 50 + return $clone; 38 51 } 39 52 40 53 public function setCollation($collation) { ··· 53 66 54 67 public function getCharacterSet() { 55 68 return $this->characterSet; 56 - } 57 - 58 - public function setName($name) { 59 - $this->name = $name; 60 - return $this; 61 - } 62 - 63 - public function getName() { 64 - return $this->name; 65 69 } 66 70 67 71 }
+90 -7
src/applications/config/schema/PhabricatorConfigSchemaQuery.php
··· 92 92 return $server_schema; 93 93 } 94 94 95 - 96 95 public function loadExpectedSchema() { 97 96 $databases = $this->getDatabaseNames(); 98 97 ··· 119 118 $utf8_collate = 'binary'; 120 119 } 121 120 121 + $specs = id(new PhutilSymbolLoader()) 122 + ->setAncestorClass('PhabricatorConfigSchemaSpec') 123 + ->loadObjects(); 124 + 122 125 $server_schema = new PhabricatorConfigServerSchema(); 123 - foreach ($databases as $database_name) { 124 - $database_schema = id(new PhabricatorConfigDatabaseSchema()) 125 - ->setName($database_name) 126 - ->setCharacterSet($utf8_charset) 127 - ->setCollation($utf8_collate); 126 + foreach ($specs as $spec) { 127 + $spec->setUTF8Charset($utf8_charset); 128 + $spec->setUTF8Collate($utf8_collate); 128 129 129 - $server_schema->addDatabase($database_schema); 130 + $spec->buildSchemata($server_schema); 130 131 } 131 132 132 133 return $server_schema; 133 134 } 135 + 136 + public function buildComparisonSchema( 137 + PhabricatorConfigServerSchema $expect, 138 + PhabricatorConfigServerSchema $actual) { 139 + 140 + $comp_server = $actual->newEmptyClone(); 141 + 142 + $all_databases = $actual->getDatabases() + $expect->getDatabases(); 143 + foreach ($all_databases as $database_name => $database_template) { 144 + $actual_database = $actual->getDatabase($database_name); 145 + $expect_database = $expect->getDatabase($database_name); 146 + 147 + $issues = $this->compareSchemata($expect_database, $actual_database); 148 + 149 + $comp_database = $database_template->newEmptyClone() 150 + ->setIssues($issues); 151 + 152 + if (!$actual_database) { 153 + $actual_database = $expect_database->newEmptyClone(); 154 + } 155 + if (!$expect_database) { 156 + $expect_database = $actual_database->newEmptyClone(); 157 + } 158 + 159 + $all_tables = 160 + $actual_database->getTables() + 161 + $expect_database->getTables(); 162 + foreach ($all_tables as $table_name => $table_template) { 163 + $actual_table = $actual_database->getTable($table_name); 164 + $expect_table = $expect_database->getTable($table_name); 165 + 166 + $issues = $this->compareSchemata($expect_table, $actual_table); 167 + 168 + $comp_table = $table_template->newEmptyClone() 169 + ->setIssues($issues); 170 + 171 + if (!$actual_table) { 172 + $actual_table = $expect_table->newEmptyClone(); 173 + } 174 + if (!$expect_table) { 175 + $expect_table = $actual_table->newEmptyClone(); 176 + } 177 + 178 + $all_columns = 179 + $actual_table->getColumns() + 180 + $expect_table->getColumns(); 181 + foreach ($all_columns as $column_name => $column_template) { 182 + $actual_column = $actual_table->getColumn($column_name); 183 + $expect_column = $expect_table->getColumn($column_name); 184 + 185 + $issues = $this->compareSchemata($expect_column, $actual_column); 186 + 187 + $comp_column = $column_template->newEmptyClone() 188 + ->setIssues($issues); 189 + 190 + $comp_table->addColumn($comp_column); 191 + } 192 + $comp_database->addTable($comp_table); 193 + } 194 + $comp_server->addDatabase($comp_database); 195 + } 196 + 197 + return $comp_server; 198 + } 199 + 200 + private function compareSchemata( 201 + PhabricatorConfigStorageSchema $expect = null, 202 + PhabricatorConfigStorageSchema $actual = null) { 203 + 204 + if (!$expect && !$actual) { 205 + throw new Exception(pht('Can not compare two missing schemata!')); 206 + } else if ($expect && !$actual) { 207 + $issues = array(PhabricatorConfigStorageSchema::ISSUE_MISSING); 208 + } else if ($actual && !$expect) { 209 + $issues = array(PhabricatorConfigStorageSchema::ISSUE_SURPLUS); 210 + } else { 211 + $issues = $actual->compareTo($expect); 212 + } 213 + 214 + return $issues; 215 + } 216 + 134 217 135 218 }
+7
src/applications/config/schema/PhabricatorConfigSchemaSpec.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigSchemaSpec extends Phobject { 4 + 5 + abstract public function buildSchemata(PhabricatorConfigServerSchema $server); 6 + 7 + }
+17 -1
src/applications/config/schema/PhabricatorConfigServerSchema.php
··· 1 1 <?php 2 2 3 - final class PhabricatorConfigServerSchema extends Phobject { 3 + final class PhabricatorConfigServerSchema 4 + extends PhabricatorConfigStorageSchema { 4 5 5 6 private $databases = array(); 6 7 ··· 20 21 21 22 public function getDatabase($key) { 22 23 return idx($this->getDatabases(), $key); 24 + } 25 + 26 + protected function getSubschemata() { 27 + return $this->getDatabases(); 28 + } 29 + 30 + public function compareToSimilarSchema( 31 + PhabricatorConfigStorageSchema $expect) { 32 + return array(); 33 + } 34 + 35 + public function newEmptyClone() { 36 + $clone = clone $this; 37 + $clone->databases = array(); 38 + return $clone; 23 39 } 24 40 25 41 }
+172
src/applications/config/schema/PhabricatorConfigStorageSchema.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigStorageSchema extends Phobject { 4 + 5 + const ISSUE_MISSING = 'missing'; 6 + const ISSUE_SURPLUS = 'surplus'; 7 + const ISSUE_CHARSET = 'charset'; 8 + const ISSUE_COLLATION = 'collation'; 9 + const ISSUE_COLUMNTYPE = 'columntype'; 10 + const ISSUE_SUBWARN = 'subwarn'; 11 + const ISSUE_SUBFAIL = 'subfail'; 12 + 13 + const STATUS_OKAY = 'okay'; 14 + const STATUS_WARN = 'warn'; 15 + const STATUS_FAIL = 'fail'; 16 + 17 + private $issues = array(); 18 + private $name; 19 + 20 + abstract public function newEmptyClone(); 21 + abstract protected function compareToSimilarSchema( 22 + PhabricatorConfigStorageSchema $expect); 23 + abstract protected function getSubschemata(); 24 + 25 + public function compareTo(PhabricatorConfigStorageSchema $expect) { 26 + if (get_class($expect) != get_class($this)) { 27 + throw new Exception(pht('Classes must match to compare schemata!')); 28 + } 29 + 30 + if ($this->getName() != $expect->getName()) { 31 + throw new Exception(pht('Names must match to compare schemata!')); 32 + } 33 + 34 + return $this->compareToSimilarSchema($expect); 35 + } 36 + 37 + public function setName($name) { 38 + $this->name = $name; 39 + return $this; 40 + } 41 + 42 + public function getName() { 43 + return $this->name; 44 + } 45 + 46 + public function setIssues(array $issues) { 47 + $this->issues = array_fuse($issues); 48 + return $this; 49 + } 50 + 51 + public function getIssues() { 52 + $issues = $this->issues; 53 + 54 + foreach ($this->getSubschemata() as $sub) { 55 + switch ($sub->getStatus()) { 56 + case self::STATUS_WARN: 57 + $issues[self::ISSUE_SUBWARN] = self::ISSUE_SUBWARN; 58 + break; 59 + case self::STATUS_FAIL: 60 + $issues[self::ISSUE_SUBFAIL] = self::ISSUE_SUBFAIL; 61 + break; 62 + } 63 + } 64 + 65 + return $issues; 66 + } 67 + 68 + public function hasIssue($issue) { 69 + return (bool)idx($this->getIssues(), $issue); 70 + } 71 + 72 + public function getAllIssues() { 73 + $issues = $this->getIssues(); 74 + foreach ($this->getSubschemata() as $sub) { 75 + $issues += $sub->getAllIssues(); 76 + } 77 + return $issues; 78 + } 79 + 80 + public function getStatus() { 81 + $status = self::STATUS_OKAY; 82 + foreach ($this->getAllIssues() as $issue) { 83 + $issue_status = self::getIssueStatus($issue); 84 + $status = self::getStrongestStatus($status, $issue_status); 85 + } 86 + return $status; 87 + } 88 + 89 + public static function getIssueName($issue) { 90 + switch ($issue) { 91 + case self::ISSUE_MISSING: 92 + return pht('Missing'); 93 + case self::ISSUE_SURPLUS: 94 + return pht('Surplus'); 95 + case self::ISSUE_CHARSET: 96 + return pht('Wrong Character Set'); 97 + case self::ISSUE_COLLATION: 98 + return pht('Wrong Collation'); 99 + case self::ISSUE_COLUMNTYPE: 100 + return pht('Wrong Column Type'); 101 + case self::ISSUE_SUBWARN: 102 + return pht('Subschemata Have Warnings'); 103 + case self::ISSUE_SUBFAIL: 104 + return pht('Subschemata Have Failures'); 105 + default: 106 + throw new Exception(pht('Unknown schema issue "%s"!', $issue)); 107 + } 108 + } 109 + 110 + public static function getIssueDescription($issue) { 111 + switch ($issue) { 112 + case self::ISSUE_MISSING: 113 + return pht('This schema is expected to exist, but does not.'); 114 + case self::ISSUE_SURPLUS: 115 + return pht('This schema is not expected to exist.'); 116 + case self::ISSUE_CHARSET: 117 + return pht('This schema can use a better character set.'); 118 + case self::ISSUE_COLLATION: 119 + return pht('This schema can use a better collation.'); 120 + case self::ISSUE_COLUMNTYPE: 121 + return pht('This schema can use a better column type.'); 122 + case self::ISSUE_SUBWARN: 123 + return pht('Subschemata have setup warnings.'); 124 + case self::ISSUE_SUBFAIL: 125 + return pht('Subschemata have setup failures.'); 126 + default: 127 + throw new Exception(pht('Unknown schema issue "%s"!', $issue)); 128 + } 129 + } 130 + 131 + public static function getIssueStatus($issue) { 132 + switch ($issue) { 133 + case self::ISSUE_MISSING: 134 + case self::ISSUE_SUBFAIL: 135 + return self::STATUS_FAIL; 136 + case self::ISSUE_SURPLUS: 137 + case self::ISSUE_CHARSET: 138 + case self::ISSUE_COLLATION: 139 + case self::ISSUE_COLUMNTYPE: 140 + case self::ISSUE_SUBWARN: 141 + return self::STATUS_WARN; 142 + default: 143 + throw new Exception(pht('Unknown schema issue "%s"!', $issue)); 144 + } 145 + } 146 + 147 + public static function getStatusSeverity($status) { 148 + switch ($status) { 149 + case self::STATUS_FAIL: 150 + return 2; 151 + case self::STATUS_WARN: 152 + return 1; 153 + case self::STATUS_OKAY: 154 + return 0; 155 + default: 156 + throw new Exception(pht('Unknown schema status "%s"!', $status)); 157 + } 158 + } 159 + 160 + public static function getStrongestStatus($u, $v) { 161 + $u_sev = self::getStatusSeverity($u); 162 + $v_sev = self::getStatusSeverity($v); 163 + 164 + if ($u_sev >= $v_sev) { 165 + return $u; 166 + } else { 167 + return $v; 168 + } 169 + } 170 + 171 + 172 + }
+23 -7
src/applications/config/schema/PhabricatorConfigTableSchema.php
··· 1 1 <?php 2 2 3 - final class PhabricatorConfigTableSchema extends Phobject { 3 + final class PhabricatorConfigTableSchema 4 + extends PhabricatorConfigStorageSchema { 4 5 5 - private $name; 6 6 private $collation; 7 7 private $columns = array(); 8 8 ··· 20 20 return $this->columns; 21 21 } 22 22 23 + public function getColumn($key) { 24 + return idx($this->getColumns(), $key); 25 + } 26 + 27 + protected function getSubschemata() { 28 + return $this->getColumns(); 29 + } 30 + 23 31 public function setCollation($collation) { 24 32 $this->collation = $collation; 25 33 return $this; ··· 29 37 return $this->collation; 30 38 } 31 39 32 - public function setName($name) { 33 - $this->name = $name; 34 - return $this; 40 + public function compareToSimilarSchema( 41 + PhabricatorConfigStorageSchema $expect) { 42 + 43 + $issues = array(); 44 + if ($this->getCollation() != $expect->getCollation()) { 45 + $issues[] = self::ISSUE_COLLATION; 46 + } 47 + 48 + return $issues; 35 49 } 36 50 37 - public function getName() { 38 - return $this->name; 51 + public function newEmptyClone() { 52 + $clone = clone $this; 53 + $clone->columns = array(); 54 + return $clone; 39 55 } 40 56 41 57 }