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

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
This commit is contained in:
epriestley 2014-09-19 11:46:30 -07:00
parent 6bfe8b5984
commit 7dabc21154
11 changed files with 958 additions and 734 deletions

View file

@ -1344,8 +1344,10 @@ phutil_register_library_map(array(
'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php',
'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php', 'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php',
'PhabricatorConfigDatabaseIssueController' => 'applications/config/controller/PhabricatorConfigDatabaseIssueController.php',
'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php', 'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php',
'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php', 'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php',
'PhabricatorConfigDatabaseStatusController' => 'applications/config/controller/PhabricatorConfigDatabaseStatusController.php',
'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php', 'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php',
'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php',
'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php', 'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php',
@ -4261,8 +4263,10 @@ phutil_register_library_map(array(
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
'PhabricatorConfigController' => 'PhabricatorController', 'PhabricatorConfigController' => 'PhabricatorController',
'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController',
'PhabricatorConfigDatabaseIssueController' => 'PhabricatorConfigDatabaseController',
'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigDatabaseStatusController' => 'PhabricatorConfigDatabaseController',
'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource',
'PhabricatorConfigEditController' => 'PhabricatorConfigController', 'PhabricatorConfigEditController' => 'PhabricatorConfigController',

View file

@ -46,7 +46,8 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
'(?:(?P<database>[^/]+)/'. '(?:(?P<database>[^/]+)/'.
'(?:(?P<table>[^/]+)/'. '(?:(?P<table>[^/]+)/'.
'(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?' '(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?'
=> 'PhabricatorConfigDatabaseController', => 'PhabricatorConfigDatabaseStatusController',
'dbissue/' => 'PhabricatorConfigDatabaseIssueController',
'(?P<verb>ignore|unignore)/(?P<key>[^/]+)/' '(?P<verb>ignore|unignore)/(?P<key>[^/]+)/'
=> 'PhabricatorConfigIgnoreController', => 'PhabricatorConfigIgnoreController',
'issue/' => array( 'issue/' => array(

View file

@ -18,6 +18,7 @@ abstract class PhabricatorConfigController extends PhabricatorController {
$nav->addFilter('issue/', pht('Setup Issues')); $nav->addFilter('issue/', pht('Setup Issues'));
$nav->addLabel(pht('Database')); $nav->addLabel(pht('Database'));
$nav->addFilter('database/', pht('Database Status')); $nav->addFilter('database/', pht('Database Status'));
$nav->addFilter('dbissue/', pht('Database Issues'));
$nav->addLabel(pht('Welcome')); $nav->addLabel(pht('Welcome'));
$nav->addFilter('welcome/', pht('Welcome Screen')); $nav->addFilter('welcome/', pht('Welcome Screen'));

View file

@ -1,26 +1,11 @@
<?php <?php
final class PhabricatorConfigDatabaseController abstract class PhabricatorConfigDatabaseController
extends PhabricatorConfigController { extends PhabricatorConfigController {
const MAX_INNODB_KEY_LENGTH = 767; const MAX_INNODB_KEY_LENGTH = 767;
private $database; protected function buildSchemaQuery() {
private $table;
private $column;
private $key;
public function willProcessRequest(array $data) {
$this->database = idx($data, 'database');
$this->table = idx($data, 'table');
$this->column = idx($data, 'column');
$this->key = idx($data, 'key');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$conf = PhabricatorEnv::newObjectFromConfig( $conf = PhabricatorEnv::newObjectFromConfig(
'mysql.configuration-provider', 'mysql.configuration-provider',
array($dao = null, 'w')); array($dao = null, 'w'));
@ -35,655 +20,10 @@ final class PhabricatorConfigDatabaseController
$query = id(new PhabricatorConfigSchemaQuery()) $query = id(new PhabricatorConfigSchemaQuery())
->setAPI($api); ->setAPI($api);
$actual = $query->loadActualSchema(); return $query;
$expect = $query->loadExpectedSchema();
$comp = $query->buildComparisonSchema($expect, $actual);
if ($this->column) {
return $this->renderColumn(
$comp,
$expect,
$actual,
$this->database,
$this->table,
$this->column);
} else if ($this->key) {
return $this->renderKey(
$comp,
$expect,
$actual,
$this->database,
$this->table,
$this->key);
} else if ($this->table) {
return $this->renderTable(
$comp,
$expect,
$actual,
$this->database,
$this->table);
} else if ($this->database) {
return $this->renderDatabase(
$comp,
$expect,
$actual,
$this->database);
} else {
return $this->renderServer(
$comp,
$expect,
$actual);
}
} }
private function buildResponse($title, $body) { protected function renderIcon($status) {
$nav = $this->buildSideNavView();
$nav->selectFilter('database/');
$crumbs = $this->buildApplicationCrumbs();
if ($this->database) {
$crumbs->addTextCrumb(
pht('Database Status'),
$this->getApplicationURI('database/'));
if ($this->table) {
$crumbs->addTextCrumb(
$this->database,
$this->getApplicationURI('database/'.$this->database.'/'));
if ($this->column || $this->key) {
$crumbs->addTextCrumb(
$this->table,
$this->getApplicationURI(
'database/'.$this->database.'/'.$this->table.'/'));
if ($this->column) {
$crumbs->addTextCrumb($this->column);
} else {
$crumbs->addTextCrumb($this->key);
}
} else {
$crumbs->addTextCrumb($this->table);
}
} else {
$crumbs->addTextCrumb($this->database);
}
} else {
$crumbs->addTextCrumb(pht('Database Status'));
}
$nav->setCrumbs($crumbs);
$nav->appendChild($body);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
));
}
private function renderServer(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual) {
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$rows = array();
foreach ($comp->getDatabases() as $database_name => $database) {
$actual_database = $actual->getDatabase($database_name);
if ($actual_database) {
$charset = $actual_database->getCharacterSet();
$collation = $actual_database->getCollation();
} else {
$charset = null;
$collation = null;
}
$status = $database->getStatus();
$issues = $database->getIssues();
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'/database/'.$database_name.'/'),
),
$database_name),
$this->renderAttr($charset, $database->hasIssue($charset_issue)),
$this->renderAttr($collation, $database->hasIssue($collation_issue)),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Database'),
pht('Charset'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
));
$title = pht('Database Status');
$properties = $this->buildProperties(
array(
),
$comp->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table);
return $this->buildResponse($title, $box);
}
private function renderDatabase(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name) {
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$rows = array();
foreach ($database->getTables() as $table_name => $table) {
$status = $table->getStatus();
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'/database/'.$database_name.'/'.$table_name.'/'),
),
$table_name),
$this->renderAttr(
$table->getCollation(),
$table->hasIssue($collation_issue)),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Table'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
));
$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())
->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table);
return $this->buildResponse($title, $box);
}
private function renderTable(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name) {
$type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE;
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE;
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
}
$rows = array();
foreach ($table->getColumns() as $column_name => $column) {
$expect_column = null;
if ($expect_table) {
$expect_column = $expect_table->getColumn($column_name);
}
$status = $column->getStatus();
$data_type = null;
if ($expect_column) {
$data_type = $expect_column->getDataType();
}
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'database/'.
$database_name.'/'.
$table_name.'/'.
'col/'.
$column_name.'/'),
),
$column_name),
$data_type,
$this->renderAttr(
$column->getColumnType(),
$column->hasIssue($type_issue)),
$this->renderAttr(
$column->getNullable()
? pht('Yes')
: pht('No'),
$column->hasIssue($nullable_issue)),
$this->renderAttr(
$column->getCharacterSet(),
$column->hasIssue($charset_issue)),
$this->renderAttr(
$column->getCollation(),
$column->hasIssue($collation_issue)),
);
}
$table_view = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Column'),
pht('Data Type'),
pht('Column Type'),
pht('Nullable'),
pht('Character Set'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
null,
null
));
$key_rows = array();
foreach ($table->getKeys() as $key_name => $key) {
$expect_key = null;
if ($expect_table) {
$expect_key = $expect_table->getKey($key_name);
}
$status = $key->getStatus();
$size = 0;
foreach ($key->getColumnNames() as $column_name) {
$column = $table->getColumn($column_name);
if (!$column) {
$size = 0;
break;
}
$size += $column->getKeyByteLength();
}
$size_formatted = null;
if ($size) {
$size_formatted = $this->renderAttr(
$size,
($size > self::MAX_INNODB_KEY_LENGTH));
}
$key_rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'database/'.
$database_name.'/'.
$table_name.'/'.
'key/'.
$key_name.'/'),
),
$key_name),
implode(', ', $key->getColumnNames()),
$size_formatted,
);
}
$keys_view = id(new AphrontTableView($key_rows))
->setHeaders(
array(
null,
pht('Key'),
pht('Columns'),
pht('Size'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
));
$title = pht('Database Status: %s.%s', $database_name, $table_name);
if ($actual_table) {
$actual_collation = $actual_table->getCollation();
} else {
$actual_collation = null;
}
if ($expect_table) {
$expect_collation = $expect_table->getCollation();
} else {
$expect_collation = null;
}
$properties = $this->buildProperties(
array(
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
),
$table->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table_view)
->appendChild($keys_view);
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 (!$column) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
$actual_column = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
if ($actual_table) {
$actual_column = $actual_table->getColumn($column_name);
}
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
$expect_column = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$expect_column = $expect_table->getColumn($column_name);
}
}
if ($actual_column) {
$actual_coltype = $actual_column->getColumnType();
$actual_charset = $actual_column->getCharacterSet();
$actual_collation = $actual_column->getCollation();
$actual_nullable = $actual_column->getNullable();
} else {
$actual_coltype = null;
$actual_charset = null;
$actual_collation = null;
$actual_nullable = null;
}
if ($expect_column) {
$data_type = $expect_column->getDataType();
$expect_coltype = $expect_column->getColumnType();
$expect_charset = $expect_column->getCharacterSet();
$expect_collation = $expect_column->getCollation();
$expect_nullable = $expect_column->getNullable();
} else {
$data_type = null;
$expect_coltype = null;
$expect_charset = null;
$expect_collation = null;
$expect_nullable = null;
}
$title = pht(
'Database Status: %s.%s.%s',
$database_name,
$table_name,
$column_name);
$properties = $this->buildProperties(
array(
array(
pht('Data Type'),
$data_type,
),
array(
pht('Column Type'),
$actual_coltype,
),
array(
pht('Expected Column Type'),
$expect_coltype,
),
array(
pht('Character Set'),
$actual_charset,
),
array(
pht('Expected Character Set'),
$expect_charset,
),
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
array(
pht('Nullable'),
$this->getNullableString($actual_nullable),
),
array(
pht('Expected Nullable'),
$this->getNullableString($expect_nullable),
),
),
$column->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties);
return $this->buildResponse($title, $box);
}
private function renderKey(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name,
$key_name) {
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$key = $table->getKey($key_name);
if (!$key) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
$actual_key = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
if ($actual_table) {
$actual_key = $actual_table->getKey($key_name);
}
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
$expect_key = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$expect_key = $expect_table->getKey($key_name);
}
}
if ($actual_key) {
$actual_columns = $actual_key->getColumnNames();
} else {
$actual_columns = array();
}
if ($expect_key) {
$expect_columns = $expect_key->getColumnNames();
} else {
$expect_columns = array();
}
$title = pht(
'Database Status: %s.%s (%s)',
$database_name,
$table_name,
$key_name);
$properties = $this->buildProperties(
array(
array(
pht('Columns'),
implode(', ', $actual_columns),
),
array(
pht('Expected Columns'),
implode(', ', $expect_columns),
),
),
$key->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties);
return $this->buildResponse($title, $box);
}
private function renderIcon($status) {
switch ($status) { switch ($status) {
case PhabricatorConfigStorageSchema::STATUS_OKAY: case PhabricatorConfigStorageSchema::STATUS_OKAY:
$icon = 'fa-check-circle green'; $icon = 'fa-check-circle green';
@ -704,7 +44,7 @@ final class PhabricatorConfigDatabaseController
->setIconFont($icon); ->setIconFont($icon);
} }
private function renderAttr($attr, $issue) { protected function renderAttr($attr, $issue) {
if ($issue) { if ($issue) {
return phutil_tag( return phutil_tag(
'span', 'span',
@ -717,56 +57,7 @@ final class PhabricatorConfigDatabaseController
} }
} }
private function buildProperties(array $properties, array $issues) { protected function renderBoolean($value) {
$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_NOTE:
$icon = PHUIStatusItemView::ICON_INFO;
$color = 'blue';
break;
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;
}
private function getNullableString($value) {
if ($value === null) { if ($value === null) {
return ''; return '';
} else if ($value === true) { } else if ($value === true) {

View file

@ -0,0 +1,167 @@
<?php
final class PhabricatorConfigDatabaseIssueController
extends PhabricatorConfigDatabaseController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$query = $this->buildSchemaQuery();
$actual = $query->loadActualSchema();
$expect = $query->loadExpectedSchema();
$comp = $query->buildComparisonSchema($expect, $actual);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Database Issues'));
// Collect all open issues.
$issues = array();
foreach ($comp->getDatabases() as $database_name => $database) {
foreach ($database->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
null,
null,
null,
$issue);
}
foreach ($database->getTables() as $table_name => $table) {
foreach ($table->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
$table_name,
null,
null,
$issue);
}
foreach ($table->getColumns() as $column_name => $column) {
foreach ($column->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
$table_name,
'column',
$column_name,
$issue);
}
}
foreach ($table->getKeys() as $key_name => $key) {
foreach ($key->getLocalIssues() as $issue) {
$issues[] = array(
$database_name,
$table_name,
'key',
$key_name,
$issue);
}
}
}
}
// Sort all open issues so that the most severe issues appear first.
$order = array();
$counts = array();
foreach ($issues as $key => $issue) {
$const = $issue[4];
$status = PhabricatorConfigStorageSchema::getIssueStatus($const);
$severity = PhabricatorConfigStorageSchema::getStatusSeverity($status);
$order[$key] = sprintf(
'~%d~%s%s%s',
9 - $severity,
$issue[0],
$issue[1],
$issue[3]);
if (empty($counts[$status])) {
$counts[$status] = 0;
}
$counts[$status]++;
}
asort($order);
$issues = array_select_keys($issues, array_keys($order));
// Render the issues.
$rows = array();
foreach ($issues as $issue) {
$const = $issue[4];
$rows[] = array(
$this->renderIcon(
PhabricatorConfigStorageSchema::getIssueStatus($const)),
$issue[0],
$issue[1],
$issue[2],
$issue[3],
PhabricatorConfigStorageSchema::getIssueDescription($const),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Database'),
pht('Table'),
pht('Type'),
pht('Column/Key'),
pht('Issue'),
))
->setColumnClasses(
array(
null,
null,
null,
null,
null,
'wide',
));
$errors = array();
$errors[] = pht(
'IMPORTANT: This feature is in development and the information below '.
'is not accurate! Ignore it for now. See T1191.');
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])) {
$errors[] = pht(
'Detected %s serious issue(s) with the schemata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_FAIL]));
}
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_WARN])) {
$errors[] = pht(
'Detected %s warning(s) with the schemata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_WARN]));
}
if (isset($counts[PhabricatorConfigStorageSchema::STATUS_NOTE])) {
$errors[] = pht(
'Detected %s minor issue(s) with the scheamata.',
new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_NOTE]));
}
$table_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Database Issues'))
->setFormErrors($errors)
->appendChild($table);
$nav = $this->buildSideNavView();
$nav->selectFilter('dbissue/');
$nav->appendChild(
array(
$crumbs,
$table_box,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => 'all',
));
}
}

View file

@ -0,0 +1,737 @@
<?php
final class PhabricatorConfigDatabaseStatusController
extends PhabricatorConfigDatabaseController {
private $database;
private $table;
private $column;
private $key;
public function willProcessRequest(array $data) {
$this->database = idx($data, 'database');
$this->table = idx($data, 'table');
$this->column = idx($data, 'column');
$this->key = idx($data, 'key');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$query = $this->buildSchemaQuery();
$actual = $query->loadActualSchema();
$expect = $query->loadExpectedSchema();
$comp = $query->buildComparisonSchema($expect, $actual);
if ($this->column) {
return $this->renderColumn(
$comp,
$expect,
$actual,
$this->database,
$this->table,
$this->column);
} else if ($this->key) {
return $this->renderKey(
$comp,
$expect,
$actual,
$this->database,
$this->table,
$this->key);
} else if ($this->table) {
return $this->renderTable(
$comp,
$expect,
$actual,
$this->database,
$this->table);
} else if ($this->database) {
return $this->renderDatabase(
$comp,
$expect,
$actual,
$this->database);
} else {
return $this->renderServer(
$comp,
$expect,
$actual);
}
}
private function buildResponse($title, $body) {
$nav = $this->buildSideNavView();
$nav->selectFilter('database/');
$crumbs = $this->buildApplicationCrumbs();
if ($this->database) {
$crumbs->addTextCrumb(
pht('Database Status'),
$this->getApplicationURI('database/'));
if ($this->table) {
$crumbs->addTextCrumb(
$this->database,
$this->getApplicationURI('database/'.$this->database.'/'));
if ($this->column || $this->key) {
$crumbs->addTextCrumb(
$this->table,
$this->getApplicationURI(
'database/'.$this->database.'/'.$this->table.'/'));
if ($this->column) {
$crumbs->addTextCrumb($this->column);
} else {
$crumbs->addTextCrumb($this->key);
}
} else {
$crumbs->addTextCrumb($this->table);
}
} else {
$crumbs->addTextCrumb($this->database);
}
} else {
$crumbs->addTextCrumb(pht('Database Status'));
}
$nav->setCrumbs($crumbs);
$nav->appendChild($body);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
));
}
private function renderServer(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual) {
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$rows = array();
foreach ($comp->getDatabases() as $database_name => $database) {
$actual_database = $actual->getDatabase($database_name);
if ($actual_database) {
$charset = $actual_database->getCharacterSet();
$collation = $actual_database->getCollation();
} else {
$charset = null;
$collation = null;
}
$status = $database->getStatus();
$issues = $database->getIssues();
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'/database/'.$database_name.'/'),
),
$database_name),
$this->renderAttr($charset, $database->hasIssue($charset_issue)),
$this->renderAttr($collation, $database->hasIssue($collation_issue)),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Database'),
pht('Charset'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
));
$title = pht('Database Status');
$properties = $this->buildProperties(
array(
),
$comp->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table);
return $this->buildResponse($title, $box);
}
private function renderDatabase(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name) {
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$rows = array();
foreach ($database->getTables() as $table_name => $table) {
$status = $table->getStatus();
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'/database/'.$database_name.'/'.$table_name.'/'),
),
$table_name),
$this->renderAttr(
$table->getCollation(),
$table->hasIssue($collation_issue)),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Table'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
));
$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())
->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table);
return $this->buildResponse($title, $box);
}
private function renderTable(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name) {
$type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE;
$charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE;
$unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE;
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
}
$rows = array();
foreach ($table->getColumns() as $column_name => $column) {
$expect_column = null;
if ($expect_table) {
$expect_column = $expect_table->getColumn($column_name);
}
$status = $column->getStatus();
$data_type = null;
if ($expect_column) {
$data_type = $expect_column->getDataType();
}
$rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'database/'.
$database_name.'/'.
$table_name.'/'.
'col/'.
$column_name.'/'),
),
$column_name),
$data_type,
$this->renderAttr(
$column->getColumnType(),
$column->hasIssue($type_issue)),
$this->renderAttr(
$this->renderBoolean($column->getNullable()),
$column->hasIssue($nullable_issue)),
$this->renderAttr(
$column->getCharacterSet(),
$column->hasIssue($charset_issue)),
$this->renderAttr(
$column->getCollation(),
$column->hasIssue($collation_issue)),
);
}
$table_view = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Column'),
pht('Data Type'),
pht('Column Type'),
pht('Nullable'),
pht('Character Set'),
pht('Collation'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
null,
null
));
$key_rows = array();
foreach ($table->getKeys() as $key_name => $key) {
$expect_key = null;
if ($expect_table) {
$expect_key = $expect_table->getKey($key_name);
}
$status = $key->getStatus();
$size = 0;
foreach ($key->getColumnNames() as $column_name) {
$column = $table->getColumn($column_name);
if (!$column) {
$size = 0;
break;
}
$size += $column->getKeyByteLength();
}
$size_formatted = null;
if ($size) {
$size_formatted = $this->renderAttr(
$size,
($size > self::MAX_INNODB_KEY_LENGTH));
}
$key_rows[] = array(
$this->renderIcon($status),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI(
'database/'.
$database_name.'/'.
$table_name.'/'.
'key/'.
$key_name.'/'),
),
$key_name),
implode(', ', $key->getColumnNames()),
$this->renderAttr(
$this->renderBoolean($key->getUnique()),
$key->hasIssue($unique_issue)),
$size_formatted,
);
}
$keys_view = id(new AphrontTableView($key_rows))
->setHeaders(
array(
null,
pht('Key'),
pht('Columns'),
pht('Unique'),
pht('Size'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
null,
null,
));
$title = pht('Database Status: %s.%s', $database_name, $table_name);
if ($actual_table) {
$actual_collation = $actual_table->getCollation();
} else {
$actual_collation = null;
}
if ($expect_table) {
$expect_collation = $expect_table->getCollation();
} else {
$expect_collation = null;
}
$properties = $this->buildProperties(
array(
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
),
$table->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties)
->appendChild($table_view)
->appendChild($keys_view);
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 (!$column) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
$actual_column = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
if ($actual_table) {
$actual_column = $actual_table->getColumn($column_name);
}
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
$expect_column = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$expect_column = $expect_table->getColumn($column_name);
}
}
if ($actual_column) {
$actual_coltype = $actual_column->getColumnType();
$actual_charset = $actual_column->getCharacterSet();
$actual_collation = $actual_column->getCollation();
$actual_nullable = $actual_column->getNullable();
} else {
$actual_coltype = null;
$actual_charset = null;
$actual_collation = null;
$actual_nullable = null;
}
if ($expect_column) {
$data_type = $expect_column->getDataType();
$expect_coltype = $expect_column->getColumnType();
$expect_charset = $expect_column->getCharacterSet();
$expect_collation = $expect_column->getCollation();
$expect_nullable = $expect_column->getNullable();
} else {
$data_type = null;
$expect_coltype = null;
$expect_charset = null;
$expect_collation = null;
$expect_nullable = null;
}
$title = pht(
'Database Status: %s.%s.%s',
$database_name,
$table_name,
$column_name);
$properties = $this->buildProperties(
array(
array(
pht('Data Type'),
$data_type,
),
array(
pht('Column Type'),
$actual_coltype,
),
array(
pht('Expected Column Type'),
$expect_coltype,
),
array(
pht('Character Set'),
$actual_charset,
),
array(
pht('Expected Character Set'),
$expect_charset,
),
array(
pht('Collation'),
$actual_collation,
),
array(
pht('Expected Collation'),
$expect_collation,
),
array(
pht('Nullable'),
$this->renderBoolean($actual_nullable),
),
array(
pht('Expected Nullable'),
$this->renderBoolean($expect_nullable),
),
),
$column->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties);
return $this->buildResponse($title, $box);
}
private function renderKey(
PhabricatorConfigServerSchema $comp,
PhabricatorConfigServerSchema $expect,
PhabricatorConfigServerSchema $actual,
$database_name,
$table_name,
$key_name) {
$database = $comp->getDatabase($database_name);
if (!$database) {
return new Aphront404Response();
}
$table = $database->getTable($table_name);
if (!$table) {
return new Aphront404Response();
}
$key = $table->getKey($key_name);
if (!$key) {
return new Aphront404Response();
}
$actual_database = $actual->getDatabase($database_name);
$actual_table = null;
$actual_key = null;
if ($actual_database) {
$actual_table = $actual_database->getTable($table_name);
if ($actual_table) {
$actual_key = $actual_table->getKey($key_name);
}
}
$expect_database = $expect->getDatabase($database_name);
$expect_table = null;
$expect_key = null;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$expect_key = $expect_table->getKey($key_name);
}
}
if ($actual_key) {
$actual_columns = $actual_key->getColumnNames();
$actual_unique = $actual_key->getUnique();
} else {
$actual_columns = array();
$actual_unique = null;
}
if ($expect_key) {
$expect_columns = $expect_key->getColumnNames();
$expect_unique = $expect_key->getUnique();
} else {
$expect_columns = array();
$expect_unique = null;
}
$title = pht(
'Database Status: %s.%s (%s)',
$database_name,
$table_name,
$key_name);
$properties = $this->buildProperties(
array(
array(
pht('Unique'),
$this->renderBoolean($actual_unique),
),
array(
pht('Expected Unique'),
$this->renderBoolean($expect_unique),
),
array(
pht('Columns'),
implode(', ', $actual_columns),
),
array(
pht('Expected Columns'),
implode(', ', $expect_columns),
),
),
$key->getIssues());
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->addPropertyList($properties);
return $this->buildResponse($title, $box);
}
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_NOTE:
$icon = PHUIStatusItemView::ICON_INFO;
$color = 'blue';
break;
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

@ -4,6 +4,16 @@ final class PhabricatorConfigKeySchema
extends PhabricatorConfigStorageSchema { extends PhabricatorConfigStorageSchema {
private $columnNames; private $columnNames;
private $unique;
public function setUnique($unique) {
$this->unique = $unique;
return $this;
}
public function getUnique() {
return $this->unique;
}
public function setColumnNames(array $column_names) { public function setColumnNames(array $column_names) {
$this->columnNames = array_values($column_names); $this->columnNames = array_values($column_names);
@ -26,6 +36,10 @@ final class PhabricatorConfigKeySchema
$issues[] = self::ISSUE_KEYCOLUMNS; $issues[] = self::ISSUE_KEYCOLUMNS;
} }
if ($this->getUnique() !== $expect->getUnique()) {
$issues[] = self::ISSUE_UNIQUE;
}
return $issues; return $issues;
} }

View file

@ -69,18 +69,9 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
$column_info = array(); $column_info = array();
} }
if ($sql) { // NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain
$key_info = queryfx_all( // primary, unique, and foreign keys, so we can't use them here. We pull
$conn, // indexes later on using SHOW INDEXES.
'SELECT CONSTRAINT_NAME, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME,
ORDINAL_POSITION, POSITION_IN_UNIQUE_CONSTRAINT
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE (%Q)',
'('.implode(') OR (', $sql).')');
$key_info = igroup($key_info, 'TABLE_SCHEMA');
} else {
$key_info = array();
}
$server_schema = new PhabricatorConfigServerSchema(); $server_schema = new PhabricatorConfigServerSchema();
@ -95,8 +86,6 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
$database_column_info = idx($column_info, $database_name, array()); $database_column_info = idx($column_info, $database_name, array());
$database_column_info = igroup($database_column_info, 'TABLE_NAME'); $database_column_info = igroup($database_column_info, 'TABLE_NAME');
$database_key_info = idx($key_info, $database_name, array());
$database_key_info = igroup($database_key_info, 'TABLE_NAME');
foreach ($database_tables as $table) { foreach ($database_tables as $table) {
$table_name = $table['TABLE_NAME']; $table_name = $table['TABLE_NAME'];
@ -117,13 +106,19 @@ final class PhabricatorConfigSchemaQuery extends Phobject {
$table_schema->addColumn($column_schema); $table_schema->addColumn($column_schema);
} }
$key_parts = idx($database_key_info, $table_name, array()); $key_parts = queryfx_all(
$keys = igroup($key_parts, 'CONSTRAINT_NAME'); $conn,
'SHOW INDEXES FROM %T.%T',
$database_name,
$table_name);
$keys = igroup($key_parts, 'Key_name');
foreach ($keys as $key_name => $key_pieces) { foreach ($keys as $key_name => $key_pieces) {
$key_pieces = isort($key_pieces, 'ORDINAL_POSITION'); $key_pieces = isort($key_pieces, 'Seq_in_index');
$head = head($key_pieces);
$key_schema = id(new PhabricatorConfigKeySchema()) $key_schema = id(new PhabricatorConfigKeySchema())
->setName($key_name) ->setName($key_name)
->setColumnNames(ipull($key_pieces, 'COLUMN_NAME')); ->setColumnNames(ipull($key_pieces, 'Column_name'))
->setUnique(!$head['Non_unique']);
$table_schema->addKey($key_schema); $table_schema->addKey($key_schema);
} }

View file

@ -100,6 +100,8 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject {
$key = $this->newKey($key_name) $key = $this->newKey($key_name)
->setColumnNames(idx($key_spec, 'columns', array())); ->setColumnNames(idx($key_spec, 'columns', array()));
$key->setUnique((bool)idx($key_spec, 'unique'));
$table->addKey($key); $table->addKey($key);
} }

View file

@ -9,6 +9,7 @@ abstract class PhabricatorConfigStorageSchema extends Phobject {
const ISSUE_COLUMNTYPE = 'columntype'; const ISSUE_COLUMNTYPE = 'columntype';
const ISSUE_NULLABLE = 'nullable'; const ISSUE_NULLABLE = 'nullable';
const ISSUE_KEYCOLUMNS = 'keycolumns'; const ISSUE_KEYCOLUMNS = 'keycolumns';
const ISSUE_UNIQUE = 'unique';
const ISSUE_SUBNOTE = 'subnote'; const ISSUE_SUBNOTE = 'subnote';
const ISSUE_SUBWARN = 'subwarn'; const ISSUE_SUBWARN = 'subwarn';
const ISSUE_SUBFAIL = 'subfail'; const ISSUE_SUBFAIL = 'subfail';
@ -72,6 +73,10 @@ abstract class PhabricatorConfigStorageSchema extends Phobject {
return $issues; return $issues;
} }
public function getLocalIssues() {
return $this->issues;
}
public function hasIssue($issue) { public function hasIssue($issue) {
return (bool)idx($this->getIssues(), $issue); return (bool)idx($this->getIssues(), $issue);
} }
@ -109,6 +114,8 @@ abstract class PhabricatorConfigStorageSchema extends Phobject {
return pht('Wrong Nullable Setting'); return pht('Wrong Nullable Setting');
case self::ISSUE_KEYCOLUMNS: case self::ISSUE_KEYCOLUMNS:
return pht('Key on Wrong Columns'); return pht('Key on Wrong Columns');
case self::ISSUE_UNIQUE:
return pht('Key has Wrong Uniqueness');
case self::ISSUE_SUBNOTE: case self::ISSUE_SUBNOTE:
return pht('Subschemata Have Notices'); return pht('Subschemata Have Notices');
case self::ISSUE_SUBWARN: case self::ISSUE_SUBWARN:
@ -136,6 +143,8 @@ abstract class PhabricatorConfigStorageSchema extends Phobject {
return pht('This schema has the wrong nullable setting.'); return pht('This schema has the wrong nullable setting.');
case self::ISSUE_KEYCOLUMNS: case self::ISSUE_KEYCOLUMNS:
return pht('This schema is on the wrong columns.'); return pht('This schema is on the wrong columns.');
case self::ISSUE_UNIQUE:
return pht('This key has the wrong uniqueness setting.');
case self::ISSUE_SUBNOTE: case self::ISSUE_SUBNOTE:
return pht('Subschemata have setup notices.'); return pht('Subschemata have setup notices.');
case self::ISSUE_SUBWARN: case self::ISSUE_SUBWARN:
@ -157,6 +166,7 @@ abstract class PhabricatorConfigStorageSchema extends Phobject {
case self::ISSUE_SUBWARN: case self::ISSUE_SUBWARN:
case self::ISSUE_KEYCOLUMNS: case self::ISSUE_KEYCOLUMNS:
case self::ISSUE_NULLABLE: case self::ISSUE_NULLABLE:
case self::ISSUE_UNIQUE:
return self::STATUS_WARN; return self::STATUS_WARN;
case self::ISSUE_SUBNOTE: case self::ISSUE_SUBNOTE:
case self::ISSUE_CHARSET: case self::ISSUE_CHARSET:

View file

@ -1810,11 +1810,13 @@ abstract class LiskDAO {
case 'id': case 'id':
$default_map['PRIMARY'] = array( $default_map['PRIMARY'] = array(
'columns' => array('id'), 'columns' => array('id'),
'unique' => true,
); );
break; break;
case 'phid': case 'phid':
$default_map['key_phid'] = array( $default_map['key_phid'] = array(
'columns' => array('phid'), 'columns' => array('phid'),
'unique' => true,
); );
break; break;
} }