From 7dabc21154f9a55e5c57da0a2267c54591115700 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 19 Sep 2014 11:46:30 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 4 + .../PhabricatorConfigApplication.php | 3 +- .../PhabricatorConfigController.php | 1 + .../PhabricatorConfigDatabaseController.php | 721 +---------------- ...abricatorConfigDatabaseIssueController.php | 167 ++++ ...bricatorConfigDatabaseStatusController.php | 737 ++++++++++++++++++ .../schema/PhabricatorConfigKeySchema.php | 14 + .../schema/PhabricatorConfigSchemaQuery.php | 31 +- .../schema/PhabricatorConfigSchemaSpec.php | 2 + .../schema/PhabricatorConfigStorageSchema.php | 10 + src/infrastructure/storage/lisk/LiskDAO.php | 2 + 11 files changed, 958 insertions(+), 734 deletions(-) create mode 100644 src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php create mode 100644 src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 942df33897..0d767f4508 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1344,8 +1344,10 @@ phutil_register_library_map(array( 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', 'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php', + 'PhabricatorConfigDatabaseIssueController' => 'applications/config/controller/PhabricatorConfigDatabaseIssueController.php', 'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php', 'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php', + 'PhabricatorConfigDatabaseStatusController' => 'applications/config/controller/PhabricatorConfigDatabaseStatusController.php', 'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php', 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', 'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php', @@ -4261,8 +4263,10 @@ phutil_register_library_map(array( 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConfigController' => 'PhabricatorController', 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', + 'PhabricatorConfigDatabaseIssueController' => 'PhabricatorConfigDatabaseController', 'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', + 'PhabricatorConfigDatabaseStatusController' => 'PhabricatorConfigDatabaseController', 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 'PhabricatorConfigEditController' => 'PhabricatorConfigController', diff --git a/src/applications/config/application/PhabricatorConfigApplication.php b/src/applications/config/application/PhabricatorConfigApplication.php index 59f8f8e959..e808ccb8ed 100644 --- a/src/applications/config/application/PhabricatorConfigApplication.php +++ b/src/applications/config/application/PhabricatorConfigApplication.php @@ -46,7 +46,8 @@ final class PhabricatorConfigApplication extends PhabricatorApplication { '(?:(?P[^/]+)/'. '(?:(?P[^/]+)/'. '(?:(?:col/(?P[^/]+)|key/(?P[^/]+))/)?)?)?' - => 'PhabricatorConfigDatabaseController', + => 'PhabricatorConfigDatabaseStatusController', + 'dbissue/' => 'PhabricatorConfigDatabaseIssueController', '(?Pignore|unignore)/(?P[^/]+)/' => 'PhabricatorConfigIgnoreController', 'issue/' => array( diff --git a/src/applications/config/controller/PhabricatorConfigController.php b/src/applications/config/controller/PhabricatorConfigController.php index e64cbab801..5873270ecd 100644 --- a/src/applications/config/controller/PhabricatorConfigController.php +++ b/src/applications/config/controller/PhabricatorConfigController.php @@ -18,6 +18,7 @@ abstract class PhabricatorConfigController extends PhabricatorController { $nav->addFilter('issue/', pht('Setup Issues')); $nav->addLabel(pht('Database')); $nav->addFilter('database/', pht('Database Status')); + $nav->addFilter('dbissue/', pht('Database Issues')); $nav->addLabel(pht('Welcome')); $nav->addFilter('welcome/', pht('Welcome Screen')); diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseController.php b/src/applications/config/controller/PhabricatorConfigDatabaseController.php index 406bdda79c..c17b877082 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseController.php @@ -1,26 +1,11 @@ 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(); - + protected function buildSchemaQuery() { $conf = PhabricatorEnv::newObjectFromConfig( 'mysql.configuration-provider', array($dao = null, 'w')); @@ -35,655 +20,10 @@ final class PhabricatorConfigDatabaseController $query = id(new PhabricatorConfigSchemaQuery()) ->setAPI($api); - $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); - } + return $query; } - 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; - - $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) { + protected function renderIcon($status) { switch ($status) { case PhabricatorConfigStorageSchema::STATUS_OKAY: $icon = 'fa-check-circle green'; @@ -704,7 +44,7 @@ final class PhabricatorConfigDatabaseController ->setIconFont($icon); } - private function renderAttr($attr, $issue) { + protected function renderAttr($attr, $issue) { if ($issue) { return phutil_tag( 'span', @@ -717,56 +57,7 @@ final class PhabricatorConfigDatabaseController } } - 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; - } - - private function getNullableString($value) { + protected function renderBoolean($value) { if ($value === null) { return ''; } else if ($value === true) { diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php new file mode 100644 index 0000000000..599f36d999 --- /dev/null +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -0,0 +1,167 @@ +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', + )); + } + +} diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php new file mode 100644 index 0000000000..1e733222bf --- /dev/null +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -0,0 +1,737 @@ +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; + } + +} diff --git a/src/applications/config/schema/PhabricatorConfigKeySchema.php b/src/applications/config/schema/PhabricatorConfigKeySchema.php index 61d65a4f71..70aad32ee2 100644 --- a/src/applications/config/schema/PhabricatorConfigKeySchema.php +++ b/src/applications/config/schema/PhabricatorConfigKeySchema.php @@ -4,6 +4,16 @@ final class PhabricatorConfigKeySchema extends PhabricatorConfigStorageSchema { 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) { $this->columnNames = array_values($column_names); @@ -26,6 +36,10 @@ final class PhabricatorConfigKeySchema $issues[] = self::ISSUE_KEYCOLUMNS; } + if ($this->getUnique() !== $expect->getUnique()) { + $issues[] = self::ISSUE_UNIQUE; + } + return $issues; } diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php index 837fb7d7b4..b5c9593b72 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -69,18 +69,9 @@ final class PhabricatorConfigSchemaQuery extends Phobject { $column_info = array(); } - if ($sql) { - $key_info = queryfx_all( - $conn, - '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(); - } + // NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain + // primary, unique, and foreign keys, so we can't use them here. We pull + // indexes later on using SHOW INDEXES. $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 = 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) { $table_name = $table['TABLE_NAME']; @@ -117,13 +106,19 @@ final class PhabricatorConfigSchemaQuery extends Phobject { $table_schema->addColumn($column_schema); } - $key_parts = idx($database_key_info, $table_name, array()); - $keys = igroup($key_parts, 'CONSTRAINT_NAME'); + $key_parts = queryfx_all( + $conn, + 'SHOW INDEXES FROM %T.%T', + $database_name, + $table_name); + $keys = igroup($key_parts, 'Key_name'); 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()) ->setName($key_name) - ->setColumnNames(ipull($key_pieces, 'COLUMN_NAME')); + ->setColumnNames(ipull($key_pieces, 'Column_name')) + ->setUnique(!$head['Non_unique']); $table_schema->addKey($key_schema); } diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index 0a2aa5fbba..555f373ec2 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -100,6 +100,8 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { $key = $this->newKey($key_name) ->setColumnNames(idx($key_spec, 'columns', array())); + $key->setUnique((bool)idx($key_spec, 'unique')); + $table->addKey($key); } diff --git a/src/applications/config/schema/PhabricatorConfigStorageSchema.php b/src/applications/config/schema/PhabricatorConfigStorageSchema.php index 66a3e38464..c7fe747dd9 100644 --- a/src/applications/config/schema/PhabricatorConfigStorageSchema.php +++ b/src/applications/config/schema/PhabricatorConfigStorageSchema.php @@ -9,6 +9,7 @@ abstract class PhabricatorConfigStorageSchema extends Phobject { const ISSUE_COLUMNTYPE = 'columntype'; const ISSUE_NULLABLE = 'nullable'; const ISSUE_KEYCOLUMNS = 'keycolumns'; + const ISSUE_UNIQUE = 'unique'; const ISSUE_SUBNOTE = 'subnote'; const ISSUE_SUBWARN = 'subwarn'; const ISSUE_SUBFAIL = 'subfail'; @@ -72,6 +73,10 @@ abstract class PhabricatorConfigStorageSchema extends Phobject { return $issues; } + public function getLocalIssues() { + return $this->issues; + } + public function hasIssue($issue) { return (bool)idx($this->getIssues(), $issue); } @@ -109,6 +114,8 @@ abstract class PhabricatorConfigStorageSchema extends Phobject { return pht('Wrong Nullable Setting'); case self::ISSUE_KEYCOLUMNS: return pht('Key on Wrong Columns'); + case self::ISSUE_UNIQUE: + return pht('Key has Wrong Uniqueness'); case self::ISSUE_SUBNOTE: return pht('Subschemata Have Notices'); case self::ISSUE_SUBWARN: @@ -136,6 +143,8 @@ abstract class PhabricatorConfigStorageSchema extends Phobject { return pht('This schema has the wrong nullable setting.'); case self::ISSUE_KEYCOLUMNS: 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: return pht('Subschemata have setup notices.'); case self::ISSUE_SUBWARN: @@ -157,6 +166,7 @@ abstract class PhabricatorConfigStorageSchema extends Phobject { case self::ISSUE_SUBWARN: case self::ISSUE_KEYCOLUMNS: case self::ISSUE_NULLABLE: + case self::ISSUE_UNIQUE: return self::STATUS_WARN; case self::ISSUE_SUBNOTE: case self::ISSUE_CHARSET: diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php index d0d2d129d6..d5d1e14c11 100644 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -1810,11 +1810,13 @@ abstract class LiskDAO { case 'id': $default_map['PRIMARY'] = array( 'columns' => array('id'), + 'unique' => true, ); break; case 'phid': $default_map['key_phid'] = array( 'columns' => array('phid'), + 'unique' => true, ); break; }