1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-19 12:00:55 +01:00

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
This commit is contained in:
epriestley 2014-09-18 08:22:54 -07:00
parent 12b53e003b
commit b24e36706d
10 changed files with 607 additions and 94 deletions

View file

@ -1360,9 +1360,11 @@ phutil_register_library_map(array(
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php',
'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php', 'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php',
'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php',
'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php', 'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php',
'PhabricatorConfigStorageSchema' => 'applications/config/schema/PhabricatorConfigStorageSchema.php',
'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php',
'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php',
'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php',
@ -4216,11 +4218,11 @@ phutil_register_library_map(array(
'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
'PhabricatorConfigAllController' => 'PhabricatorConfigController', 'PhabricatorConfigAllController' => 'PhabricatorConfigController',
'PhabricatorConfigApplication' => 'PhabricatorApplication', 'PhabricatorConfigApplication' => 'PhabricatorApplication',
'PhabricatorConfigColumnSchema' => 'Phobject', 'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
'PhabricatorConfigController' => 'PhabricatorController', 'PhabricatorConfigController' => 'PhabricatorController',
'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController',
'PhabricatorConfigDatabaseSchema' => 'Phobject', 'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource',
@ -4252,9 +4254,11 @@ phutil_register_library_map(array(
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
'PhabricatorConfigResponse' => 'AphrontHTMLResponse', 'PhabricatorConfigResponse' => 'AphrontHTMLResponse',
'PhabricatorConfigSchemaQuery' => 'Phobject', 'PhabricatorConfigSchemaQuery' => 'Phobject',
'PhabricatorConfigServerSchema' => 'Phobject', 'PhabricatorConfigSchemaSpec' => 'Phobject',
'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigStackSource' => 'PhabricatorConfigSource', 'PhabricatorConfigStackSource' => 'PhabricatorConfigSource',
'PhabricatorConfigTableSchema' => 'Phobject', 'PhabricatorConfigStorageSchema' => 'Phobject',
'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorConfigValidationException' => 'Exception', 'PhabricatorConfigValidationException' => 'Exception',

View file

@ -42,7 +42,10 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController', 'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController',
'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController', 'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController',
'welcome/' => 'PhabricatorConfigWelcomeController', 'welcome/' => 'PhabricatorConfigWelcomeController',
'database/(?:(?P<database>[^/]+)/(?:(?P<table>[^/]+)/)?)?' 'database/'.
'(?:(?P<database>[^/]+)/'.
'(?:(?P<table>[^/]+)/'.
'(?:(?P<column>[^/]+)/)?)?)?'
=> 'PhabricatorConfigDatabaseController', => 'PhabricatorConfigDatabaseController',
'(?P<verb>ignore|unignore)/(?P<key>[^/]+)/' '(?P<verb>ignore|unignore)/(?P<key>[^/]+)/'
=> 'PhabricatorConfigIgnoreController', => 'PhabricatorConfigIgnoreController',

View file

@ -5,10 +5,12 @@ final class PhabricatorConfigDatabaseController
private $database; private $database;
private $table; private $table;
private $column;
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->database = idx($data, 'database'); $this->database = idx($data, 'database');
$this->table = idx($data, 'table'); $this->table = idx($data, 'table');
$this->column = idx($data, 'column');
} }
public function processRequest() { public function processRequest() {
@ -31,22 +33,34 @@ final class PhabricatorConfigDatabaseController
$actual = $query->loadActualSchema(); $actual = $query->loadActualSchema();
$expect = $query->loadExpectedSchema(); $expect = $query->loadExpectedSchema();
$comp = $query->buildComparisonSchema($expect, $actual);
if ($this->table) { if ($this->column) {
return $this->renderTable( return $this->renderColumn(
$actual, $comp,
$expect, $expect,
$actual,
$this->database,
$this->table,
$this->column);
} else if ($this->table) {
return $this->renderTable(
$comp,
$expect,
$actual,
$this->database, $this->database,
$this->table); $this->table);
} else if ($this->database) { } else if ($this->database) {
return $this->renderDatabase( return $this->renderDatabase(
$actual, $comp,
$expect, $expect,
$actual,
$this->database); $this->database);
} else { } else {
return $this->renderServer( return $this->renderServer(
$actual, $comp,
$expect); $expect,
$actual);
} }
} }
@ -83,41 +97,29 @@ final class PhabricatorConfigDatabaseController
private function renderServer( private function renderServer(
PhabricatorConfigServerSchema $schema, PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect) { PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual) {
$icon_ok = id(new PHUIIconView()) $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
->setIconFont('fa-check-circle green'); $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$icon_warn = id(new PHUIIconView())
->setIconFont('fa-exclamation-circle yellow');
$rows = array(); $rows = array();
foreach ($schema->getDatabases() as $database_name => $database) { foreach ($comp->getDatabases() as $database_name => $database) {
$actual_database = $actual->getDatabase($database_name);
$expect_database = $expect->getDatabase($database_name); if ($actual_database) {
if ($expect_database) { $charset = $actual_database->getCharacterSet();
$expect_set = $expect_database->getCharacterSet(); $collation = $actual_database->getCollation();
$expect_collation = $expect_database->getCollation();
if ($database->isSameSchema($expect_database)) {
$icon = $icon_ok;
} else {
$icon = $icon_warn;
}
} else { } else {
$expect_set = null; $charset = null;
$expect_collation = null; $collation = null;
$icon = $icon_warn;
} }
$actual_set = $database->getCharacterSet(); $status = $database->getStatus();
$actual_collation = $database->getCollation(); $issues = $database->getIssues();
$rows[] = array( $rows[] = array(
$icon, $this->renderIcon($status),
phutil_tag( phutil_tag(
'a', 'a',
array( array(
@ -125,10 +127,8 @@ final class PhabricatorConfigDatabaseController
'/database/'.$database_name.'/'), '/database/'.$database_name.'/'),
), ),
$database_name), $database_name),
$actual_set, $this->renderAttr($charset, $database->hasIssue($charset_issue)),
$expect_set, $this->renderAttr($collation, $database->hasIssue($collation_issue)),
$actual_collation,
$expect_collation,
); );
} }
@ -138,13 +138,11 @@ final class PhabricatorConfigDatabaseController
null, null,
pht('Database'), pht('Database'),
pht('Charset'), pht('Charset'),
pht('Expected Charset'),
pht('Collation'), pht('Collation'),
pht('Expected Collation'),
)) ))
->setColumnClasses( ->setColumnClasses(
array( array(
'', null,
'wide pri', 'wide pri',
null, null,
null, null,
@ -152,26 +150,38 @@ final class PhabricatorConfigDatabaseController
$title = pht('Database Status'); $title = pht('Database Status');
$properties = $this->buildProperties(
array(
),
$comp->getIssues());
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeaderText($title) ->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table); ->appendChild($table);
return $this->buildResponse($title, $box); return $this->buildResponse($title, $box);
} }
private function renderDatabase( private function renderDatabase(
PhabricatorConfigServerSchema $schema, PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect, PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name) { $database_name) {
$database = $schema->getDatabase($database_name); $database = $comp->getDatabase($database_name);
if (!$database) { if (!$database) {
return new Aphront404Response(); return new Aphront404Response();
} }
$rows = array(); $rows = array();
foreach ($database->getTables() as $table_name => $table) { foreach ($database->getTables() as $table_name => $table) {
$status = $table->getStatus();
$issues = $table->getIssues();
$rows[] = array( $rows[] = array(
$this->renderIcon($status),
phutil_tag( phutil_tag(
'a', 'a',
array( array(
@ -186,31 +196,74 @@ final class PhabricatorConfigDatabaseController
$table = id(new AphrontTableView($rows)) $table = id(new AphrontTableView($rows))
->setHeaders( ->setHeaders(
array( array(
null,
pht('Table'), pht('Table'),
pht('Collation'), pht('Collation'),
)) ))
->setColumnClasses( ->setColumnClasses(
array( array(
null,
'wide pri', 'wide pri',
null, null,
)); ));
$title = pht('Database Status: %s', $database_name); $title = pht('Database Status: %s', $database_name);
$actual_database = $actual->getDatabase($database_name);
if ($actual_database) {
$actual_charset = $actual_database->getCharacterSet();
$actual_collation = $actual_database->getCollation();
} else {
$actual_charset = null;
$actual_collation = null;
}
$expect_database = $expect->getDatabase($database_name);
if ($expect_database) {
$expect_charset = $expect_database->getCharacterSet();
$expect_collation = $expect_database->getCollation();
} else {
$expect_charset = null;
$expect_collation = null;
}
$properties = $this->buildProperties(
array(
array(
pht('Character Set'),
$actual_charset,
),
array(
pht('Expected Character Set'),
$expect_charset,
),
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
),
$database->getIssues());
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeaderText($title) ->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table); ->appendChild($table);
return $this->buildResponse($title, $box); return $this->buildResponse($title, $box);
} }
private function renderTable( private function renderTable(
PhabricatorConfigServerSchema $schema, PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect, PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name, $database_name,
$table_name) { $table_name) {
$database = $schema->getDatabase($database_name); $database = $comp->getDatabase($database_name);
if (!$database) { if (!$database) {
return new Aphront404Response(); return new Aphront404Response();
} }
@ -222,17 +275,30 @@ final class PhabricatorConfigDatabaseController
$rows = array(); $rows = array();
foreach ($table->getColumns() as $column_name => $column) { foreach ($table->getColumns() as $column_name => $column) {
$status = $column->getStatus();
$rows[] = array( $rows[] = array(
$column_name, $this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'database/'.
$database_name.'/'.
$table_name.'/'.
$column_name.'/'),
),
$column_name),
$column->getColumnType(), $column->getColumnType(),
$column->getCharacterSet(), $column->getCharacterSet(),
$column->getCollation(), $column->getCollation(),
); );
} }
$table = id(new AphrontTableView($rows)) $table_view = id(new AphrontTableView($rows))
->setHeaders( ->setHeaders(
array( array(
null,
pht('Table'), pht('Table'),
pht('Column Type'), pht('Column Type'),
pht('Character Set'), pht('Character Set'),
@ -240,6 +306,7 @@ final class PhabricatorConfigDatabaseController
)) ))
->setColumnClasses( ->setColumnClasses(
array( array(
null,
'wide pri', 'wide pri',
null, null,
null, null,
@ -248,11 +315,133 @@ final class PhabricatorConfigDatabaseController
$title = pht('Database Status: %s.%s', $database_name, $table_name); $title = pht('Database Status: %s.%s', $database_name, $table_name);
$properties = $this->buildProperties(
array(
),
$table->getIssues());
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeaderText($title) ->setHeaderText($title)
->appendChild($table); ->addPropertyList($properties)
->appendChild($table_view);
return $this->buildResponse($title, $box); return $this->buildResponse($title, $box);
} }
private function renderColumn(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name,
$column_name) {
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$column = $table->getColumn($column_name);
if (!$table) {
return new Aphront404Response();
}
$title = pht(
'Database Status: %s.%s.%s',
$database_name,
$table_name,
$column_name);
$properties = $this->buildProperties(
array(
),
$column->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties);
return $this->buildResponse($title, $box);
}
private function renderIcon($status) {
switch ($status) {
case PhabricatorConfigStorageSchema::STATUS_OKAY:
$icon = 'fa-check-circle green';
break;
case PhabricatorConfigStorageSchema::STATUS_WARN:
$icon = 'fa-exclamation-circle yellow';
break;
case PhabricatorConfigStorageSchema::STATUS_FAIL:
default:
$icon = 'fa-times-circle red';
break;
}
return id(new PHUIIconView())
->setIconFont($icon);
}
private function renderAttr($attr, $issue) {
if ($issue) {
return phutil_tag(
'span',
array(
'style' => 'color: #aa0000;',
),
$attr);
} else {
return $attr;
}
}
private function buildProperties(array $properties, array $issues) {
$view = id(new PHUIPropertyListView())
->setUser($this->getRequest()->getUser());
foreach ($properties as $property) {
list($key, $value) = $property;
$view->addProperty($key, $value);
}
$status_view = new PHUIStatusListView();
if (!$issues) {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('No Schema Issues')));
} else {
foreach ($issues as $issue) {
$note = PhabricatorConfigStorageSchema::getIssueDescription($issue);
$status = PhabricatorConfigStorageSchema::getIssueStatus($issue);
switch ($status) {
case PhabricatorConfigStorageSchema::STATUS_WARN:
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'yellow';
break;
case PhabricatorConfigStorageSchema::STATUS_FAIL:
default:
$icon = PHUIStatusItemView::ICON_REJECT;
$color = 'red';
break;
}
$item = id(new PHUIStatusItemView())
->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue))
->setIcon($icon, $color)
->setNote($note);
$status_view->addItem($item);
}
}
$view->addProperty(pht('Schema Status'), $status_view);
return $view;
}
} }

View file

@ -1,8 +1,8 @@
<?php <?php
final class PhabricatorConfigColumnSchema extends Phobject { final class PhabricatorConfigColumnSchema
extends PhabricatorConfigStorageSchema {
private $name;
private $characterSet; private $characterSet;
private $collation; private $collation;
private $columnType; private $columnType;
@ -16,6 +16,10 @@ final class PhabricatorConfigColumnSchema extends Phobject {
return $this->columnType; return $this->columnType;
} }
protected function getSubschemata() {
return array();
}
public function setCollation($collation) { public function setCollation($collation) {
$this->collation = $collation; $this->collation = $collation;
return $this; return $this;
@ -34,13 +38,28 @@ final class PhabricatorConfigColumnSchema extends Phobject {
return $this->characterSet; return $this->characterSet;
} }
public function setName($name) { public function compareToSimilarSchema(
$this->name = $name; PhabricatorConfigStorageSchema $expect) {
return $this;
$issues = array();
if ($this->getCharacterSet() != $expect->getCharacterSet()) {
$issues[] = self::ISSUE_CHARSET;
}
if ($this->getCollation() != $expect->getCollation()) {
$issues[] = self::ISSUE_COLLATION;
}
if ($this->getColumnType() != $expect->getColumnType()) {
$issues[] = self::ISSUE_COLUMNTYPE;
}
return $issues;
} }
public function getName() { public function newEmptyClone() {
return $this->name; $clone = clone $this;
return $clone;
} }
} }

View file

@ -1,8 +1,8 @@
<?php <?php
final class PhabricatorConfigDatabaseSchema extends Phobject { final class PhabricatorConfigDatabaseSchema
extends PhabricatorConfigStorageSchema {
private $name;
private $characterSet; private $characterSet;
private $collation; private $collation;
private $tables = array(); private $tables = array();
@ -25,16 +25,29 @@ final class PhabricatorConfigDatabaseSchema extends Phobject {
return idx($this->tables, $key); return idx($this->tables, $key);
} }
public function isSameSchema(PhabricatorConfigDatabaseSchema $expect) { protected function getSubschemata() {
return ($this->toDictionary() === $expect->toDictionary()); return $this->getTables();
} }
public function toDictionary() { public function compareToSimilarSchema(
return array( PhabricatorConfigStorageSchema $expect) {
'name' => $this->getName(),
'characterSet' => $this->getCharacterSet(), $issues = array();
'collation' => $this->getCollation(), if ($this->getCharacterSet() != $expect->getCharacterSet()) {
); $issues[] = self::ISSUE_CHARSET;
}
if ($this->getCollation() != $expect->getCollation()) {
$issues[] = self::ISSUE_COLLATION;
}
return $issues;
}
public function newEmptyClone() {
$clone = clone $this;
$clone->tables = array();
return $clone;
} }
public function setCollation($collation) { public function setCollation($collation) {
@ -55,13 +68,4 @@ final class PhabricatorConfigDatabaseSchema extends Phobject {
return $this->characterSet; return $this->characterSet;
} }
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
} }

View file

@ -92,7 +92,6 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
return $server_schema; return $server_schema;
} }
public function loadExpectedSchema() { public function loadExpectedSchema() {
$databases = $this->getDatabaseNames(); $databases = $this->getDatabaseNames();
@ -119,17 +118,101 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
$utf8_collate = 'binary'; $utf8_collate = 'binary';
} }
$server_schema = new PhabricatorConfigServerSchema(); $specs = id(new PhutilSymbolLoader())
foreach ($databases as $database_name) { ->setAncestorClass('PhabricatorConfigSchemaSpec')
$database_schema = id(new PhabricatorConfigDatabaseSchema()) ->loadObjects();
->setName($database_name)
->setCharacterSet($utf8_charset)
->setCollation($utf8_collate);
$server_schema->addDatabase($database_schema); $server_schema = new PhabricatorConfigServerSchema();
foreach ($specs as $spec) {
$spec->setUTF8Charset($utf8_charset);
$spec->setUTF8Collate($utf8_collate);
$spec->buildSchemata($server_schema);
} }
return $server_schema; return $server_schema;
} }
public function buildComparisonSchema(
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual) {
$comp_server = $actual->newEmptyClone();
$all_databases = $actual->getDatabases() + $expect->getDatabases();
foreach ($all_databases as $database_name => $database_template) {
$actual_database = $actual->getDatabase($database_name);
$expect_database = $expect->getDatabase($database_name);
$issues = $this->compareSchemata($expect_database, $actual_database);
$comp_database = $database_template->newEmptyClone()
->setIssues($issues);
if (!$actual_database) {
$actual_database = $expect_database->newEmptyClone();
}
if (!$expect_database) {
$expect_database = $actual_database->newEmptyClone();
}
$all_tables =
$actual_database->getTables() +
$expect_database->getTables();
foreach ($all_tables as $table_name => $table_template) {
$actual_table = $actual_database->getTable($table_name);
$expect_table = $expect_database->getTable($table_name);
$issues = $this->compareSchemata($expect_table, $actual_table);
$comp_table = $table_template->newEmptyClone()
->setIssues($issues);
if (!$actual_table) {
$actual_table = $expect_table->newEmptyClone();
}
if (!$expect_table) {
$expect_table = $actual_table->newEmptyClone();
}
$all_columns =
$actual_table->getColumns() +
$expect_table->getColumns();
foreach ($all_columns as $column_name => $column_template) {
$actual_column = $actual_table->getColumn($column_name);
$expect_column = $expect_table->getColumn($column_name);
$issues = $this->compareSchemata($expect_column, $actual_column);
$comp_column = $column_template->newEmptyClone()
->setIssues($issues);
$comp_table->addColumn($comp_column);
}
$comp_database->addTable($comp_table);
}
$comp_server->addDatabase($comp_database);
}
return $comp_server;
}
private function compareSchemata(
PhabricatorConfigStorageSchema $expect = null,
PhabricatorConfigStorageSchema $actual = null) {
if (!$expect && !$actual) {
throw new Exception(pht('Can not compare two missing schemata!'));
} else if ($expect && !$actual) {
$issues = array(PhabricatorConfigStorageSchema::ISSUE_MISSING);
} else if ($actual && !$expect) {
$issues = array(PhabricatorConfigStorageSchema::ISSUE_SURPLUS);
} else {
$issues = $actual->compareTo($expect);
}
return $issues;
}
} }

View file

@ -0,0 +1,7 @@
<?php
abstract class PhabricatorConfigSchemaSpec extends Phobject {
abstract public function buildSchemata(PhabricatorConfigServerSchema $server);
}

View file

@ -1,6 +1,7 @@
<?php <?php
final class PhabricatorConfigServerSchema extends Phobject { final class PhabricatorConfigServerSchema
extends PhabricatorConfigStorageSchema {
private $databases = array(); private $databases = array();
@ -22,4 +23,19 @@ final class PhabricatorConfigServerSchema extends Phobject {
return idx($this->getDatabases(), $key); return idx($this->getDatabases(), $key);
} }
protected function getSubschemata() {
return $this->getDatabases();
}
public function compareToSimilarSchema(
PhabricatorConfigStorageSchema $expect) {
return array();
}
public function newEmptyClone() {
$clone = clone $this;
$clone->databases = array();
return $clone;
}
} }

View file

@ -0,0 +1,172 @@
<?php
abstract class PhabricatorConfigStorageSchema extends Phobject {
const ISSUE_MISSING = 'missing';
const ISSUE_SURPLUS = 'surplus';
const ISSUE_CHARSET = 'charset';
const ISSUE_COLLATION = 'collation';
const ISSUE_COLUMNTYPE = 'columntype';
const ISSUE_SUBWARN = 'subwarn';
const ISSUE_SUBFAIL = 'subfail';
const STATUS_OKAY = 'okay';
const STATUS_WARN = 'warn';
const STATUS_FAIL = 'fail';
private $issues = array();
private $name;
abstract public function newEmptyClone();
abstract protected function compareToSimilarSchema(
PhabricatorConfigStorageSchema $expect);
abstract protected function getSubschemata();
public function compareTo(PhabricatorConfigStorageSchema $expect) {
if (get_class($expect) != get_class($this)) {
throw new Exception(pht('Classes must match to compare schemata!'));
}
if ($this->getName() != $expect->getName()) {
throw new Exception(pht('Names must match to compare schemata!'));
}
return $this->compareToSimilarSchema($expect);
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setIssues(array $issues) {
$this->issues = array_fuse($issues);
return $this;
}
public function getIssues() {
$issues = $this->issues;
foreach ($this->getSubschemata() as $sub) {
switch ($sub->getStatus()) {
case self::STATUS_WARN:
$issues[self::ISSUE_SUBWARN] = self::ISSUE_SUBWARN;
break;
case self::STATUS_FAIL:
$issues[self::ISSUE_SUBFAIL] = self::ISSUE_SUBFAIL;
break;
}
}
return $issues;
}
public function hasIssue($issue) {
return (bool)idx($this->getIssues(), $issue);
}
public function getAllIssues() {
$issues = $this->getIssues();
foreach ($this->getSubschemata() as $sub) {
$issues += $sub->getAllIssues();
}
return $issues;
}
public function getStatus() {
$status = self::STATUS_OKAY;
foreach ($this->getAllIssues() as $issue) {
$issue_status = self::getIssueStatus($issue);
$status = self::getStrongestStatus($status, $issue_status);
}
return $status;
}
public static function getIssueName($issue) {
switch ($issue) {
case self::ISSUE_MISSING:
return pht('Missing');
case self::ISSUE_SURPLUS:
return pht('Surplus');
case self::ISSUE_CHARSET:
return pht('Wrong Character Set');
case self::ISSUE_COLLATION:
return pht('Wrong Collation');
case self::ISSUE_COLUMNTYPE:
return pht('Wrong Column Type');
case self::ISSUE_SUBWARN:
return pht('Subschemata Have Warnings');
case self::ISSUE_SUBFAIL:
return pht('Subschemata Have Failures');
default:
throw new Exception(pht('Unknown schema issue "%s"!', $issue));
}
}
public static function getIssueDescription($issue) {
switch ($issue) {
case self::ISSUE_MISSING:
return pht('This schema is expected to exist, but does not.');
case self::ISSUE_SURPLUS:
return pht('This schema is not expected to exist.');
case self::ISSUE_CHARSET:
return pht('This schema can use a better character set.');
case self::ISSUE_COLLATION:
return pht('This schema can use a better collation.');
case self::ISSUE_COLUMNTYPE:
return pht('This schema can use a better column type.');
case self::ISSUE_SUBWARN:
return pht('Subschemata have setup warnings.');
case self::ISSUE_SUBFAIL:
return pht('Subschemata have setup failures.');
default:
throw new Exception(pht('Unknown schema issue "%s"!', $issue));
}
}
public static function getIssueStatus($issue) {
switch ($issue) {
case self::ISSUE_MISSING:
case self::ISSUE_SUBFAIL:
return self::STATUS_FAIL;
case self::ISSUE_SURPLUS:
case self::ISSUE_CHARSET:
case self::ISSUE_COLLATION:
case self::ISSUE_COLUMNTYPE:
case self::ISSUE_SUBWARN:
return self::STATUS_WARN;
default:
throw new Exception(pht('Unknown schema issue "%s"!', $issue));
}
}
public static function getStatusSeverity($status) {
switch ($status) {
case self::STATUS_FAIL:
return 2;
case self::STATUS_WARN:
return 1;
case self::STATUS_OKAY:
return 0;
default:
throw new Exception(pht('Unknown schema status "%s"!', $status));
}
}
public static function getStrongestStatus($u, $v) {
$u_sev = self::getStatusSeverity($u);
$v_sev = self::getStatusSeverity($v);
if ($u_sev >= $v_sev) {
return $u;
} else {
return $v;
}
}
}

View file

@ -1,8 +1,8 @@
<?php <?php
final class PhabricatorConfigTableSchema extends Phobject { final class PhabricatorConfigTableSchema
extends PhabricatorConfigStorageSchema {
private $name;
private $collation; private $collation;
private $columns = array(); private $columns = array();
@ -20,6 +20,14 @@ final class PhabricatorConfigTableSchema extends Phobject {
return $this->columns; return $this->columns;
} }
public function getColumn($key) {
return idx($this->getColumns(), $key);
}
protected function getSubschemata() {
return $this->getColumns();
}
public function setCollation($collation) { public function setCollation($collation) {
$this->collation = $collation; $this->collation = $collation;
return $this; return $this;
@ -29,13 +37,21 @@ final class PhabricatorConfigTableSchema extends Phobject {
return $this->collation; return $this->collation;
} }
public function setName($name) { public function compareToSimilarSchema(
$this->name = $name; PhabricatorConfigStorageSchema $expect) {
return $this;
$issues = array();
if ($this->getCollation() != $expect->getCollation()) {
$issues[] = self::ISSUE_COLLATION;
}
return $issues;
} }
public function getName() { public function newEmptyClone() {
return $this->name; $clone = clone $this;
$clone->columns = array();
return $clone;
} }
} }