diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1344,8 +1344,10 @@ '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 @@ '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 --- a/src/applications/config/application/PhabricatorConfigApplication.php +++ b/src/applications/config/application/PhabricatorConfigApplication.php @@ -46,7 +46,8 @@ '(?:(?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 --- a/src/applications/config/controller/PhabricatorConfigController.php +++ b/src/applications/config/controller/PhabricatorConfigController.php @@ -18,6 +18,7 @@ $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 --- 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 @@ $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); - } - } - - 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); + return $query; } - 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 @@ ->setIconFont($icon); } - private function renderAttr($attr, $issue) { + protected function renderAttr($attr, $issue) { if ($issue) { return phutil_tag( 'span', @@ -717,56 +57,7 @@ } } - 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 --- /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/PhabricatorConfigDatabaseController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php copy from src/applications/config/controller/PhabricatorConfigDatabaseController.php copy to src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php --- a/src/applications/config/controller/PhabricatorConfigDatabaseController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -1,9 +1,7 @@ getRequest(); $viewer = $request->getUser(); - $conf = PhabricatorEnv::newObjectFromConfig( - 'mysql.configuration-provider', - array($dao = null, 'w')); - - $api = id(new PhabricatorStorageManagementAPI()) - ->setUser($conf->getUser()) - ->setHost($conf->getHost()) - ->setPort($conf->getPort()) - ->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace()) - ->setPassword($conf->getPassword()); - - $query = id(new PhabricatorConfigSchemaQuery()) - ->setAPI($api); + $query = $this->buildSchemaQuery(); $actual = $query->loadActualSchema(); $expect = $query->loadExpectedSchema(); @@ -293,6 +279,7 @@ $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) { @@ -348,9 +335,7 @@ $column->getColumnType(), $column->hasIssue($type_issue)), $this->renderAttr( - $column->getNullable() - ? pht('Yes') - : pht('No'), + $this->renderBoolean($column->getNullable()), $column->hasIssue($nullable_issue)), $this->renderAttr( $column->getCharacterSet(), @@ -422,6 +407,9 @@ ), $key_name), implode(', ', $key->getColumnNames()), + $this->renderAttr( + $this->renderBoolean($key->getUnique()), + $key->hasIssue($unique_issue)), $size_formatted, ); } @@ -432,6 +420,7 @@ null, pht('Key'), pht('Columns'), + pht('Unique'), pht('Size'), )) ->setColumnClasses( @@ -440,6 +429,7 @@ 'wide pri', null, null, + null, )); $title = pht('Database Status: %s.%s', $database_name, $table_name); @@ -586,11 +576,11 @@ ), array( pht('Nullable'), - $this->getNullableString($actual_nullable), + $this->renderBoolean($actual_nullable), ), array( pht('Expected Nullable'), - $this->getNullableString($expect_nullable), + $this->renderBoolean($expect_nullable), ), ), $column->getIssues()); @@ -647,14 +637,18 @@ 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( @@ -666,6 +660,14 @@ $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), ), @@ -683,40 +685,6 @@ return $this->buildResponse($title, $box); } - private function renderIcon($status) { - switch ($status) { - case PhabricatorConfigStorageSchema::STATUS_OKAY: - $icon = 'fa-check-circle green'; - break; - case PhabricatorConfigStorageSchema::STATUS_NOTE: - $icon = 'fa-info-circle blue'; - break; - case PhabricatorConfigStorageSchema::STATUS_WARN: - $icon = 'fa-exclamation-circle yellow'; - break; - case PhabricatorConfigStorageSchema::STATUS_FAIL: - default: - $icon = 'fa-times-circle red'; - break; - } - - return id(new PHUIIconView()) - ->setIconFont($icon); - } - - private function renderAttr($attr, $issue) { - if ($issue) { - return phutil_tag( - 'span', - array( - 'style' => 'color: #aa0000;', - ), - $attr); - } else { - return $attr; - } - } - private function buildProperties(array $properties, array $issues) { $view = id(new PHUIPropertyListView()) ->setUser($this->getRequest()->getUser()); @@ -766,14 +734,4 @@ return $view; } - private function getNullableString($value) { - if ($value === null) { - return ''; - } else if ($value === true) { - return pht('Yes'); - } else { - return pht('No'); - } - } - } diff --git a/src/applications/config/schema/PhabricatorConfigKeySchema.php b/src/applications/config/schema/PhabricatorConfigKeySchema.php --- a/src/applications/config/schema/PhabricatorConfigKeySchema.php +++ b/src/applications/config/schema/PhabricatorConfigKeySchema.php @@ -4,6 +4,16 @@ 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 @@ $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 --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -69,18 +69,9 @@ $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 @@ $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 @@ $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 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -100,6 +100,8 @@ $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 --- a/src/applications/config/schema/PhabricatorConfigStorageSchema.php +++ b/src/applications/config/schema/PhabricatorConfigStorageSchema.php @@ -9,6 +9,7 @@ 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 @@ return $issues; } + public function getLocalIssues() { + return $this->issues; + } + public function hasIssue($issue) { return (bool)idx($this->getIssues(), $issue); } @@ -109,6 +114,8 @@ 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 @@ 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 @@ 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 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -1810,11 +1810,13 @@ 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; }