@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a UI for reviewing database schemata

Summary:
Ref T1191. Plan here is:

- Build a tool showing the current schemata status (this diff).
- Have it compare the current status to the desired status (partly here, mostly in future diffs).
- Then add a migration tool, and eventually a setup issue to tell people to run it.

Test Plan:
Reviewed current schemata.

{F204492}

{F204493}

{F204494}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1191

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

+599
+12
src/__phutil_library_map__.php
··· 1328 1328 'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php', 1329 1329 'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php', 1330 1330 'PhabricatorConfigApplication' => 'applications/config/application/PhabricatorConfigApplication.php', 1331 + 'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php', 1331 1332 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', 1332 1333 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', 1334 + 'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php', 1335 + 'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php', 1333 1336 'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php', 1334 1337 'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php', 1335 1338 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', ··· 1356 1359 'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php', 1357 1360 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 1358 1361 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 1362 + 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 1363 + 'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php', 1359 1364 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', 1360 1365 'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php', 1366 + 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 1361 1367 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 1362 1368 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', 1363 1369 'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php', ··· 4210 4216 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 4211 4217 'PhabricatorConfigAllController' => 'PhabricatorConfigController', 4212 4218 'PhabricatorConfigApplication' => 'PhabricatorApplication', 4219 + 'PhabricatorConfigColumnSchema' => 'Phobject', 4213 4220 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 4214 4221 'PhabricatorConfigController' => 'PhabricatorController', 4222 + 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', 4223 + 'PhabricatorConfigDatabaseSchema' => 'Phobject', 4215 4224 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', 4216 4225 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 4217 4226 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', ··· 4242 4251 ), 4243 4252 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 4244 4253 'PhabricatorConfigResponse' => 'AphrontHTMLResponse', 4254 + 'PhabricatorConfigSchemaQuery' => 'Phobject', 4255 + 'PhabricatorConfigServerSchema' => 'Phobject', 4245 4256 'PhabricatorConfigStackSource' => 'PhabricatorConfigSource', 4257 + 'PhabricatorConfigTableSchema' => 'Phobject', 4246 4258 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 4247 4259 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4248 4260 'PhabricatorConfigValidationException' => 'Exception',
+2
src/applications/config/application/PhabricatorConfigApplication.php
··· 42 42 'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController', 43 43 'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController', 44 44 'welcome/' => 'PhabricatorConfigWelcomeController', 45 + 'database/(?:(?P<database>[^/]+)/(?:(?P<table>[^/]+)/)?)?' 46 + => 'PhabricatorConfigDatabaseController', 45 47 '(?P<verb>ignore|unignore)/(?P<key>[^/]+)/' 46 48 => 'PhabricatorConfigIgnoreController', 47 49 'issue/' => array(
+1
src/applications/config/controller/PhabricatorConfigController.php
··· 15 15 $nav->addFilter('/', pht('Option Groups')); 16 16 $nav->addFilter('all/', pht('All Settings')); 17 17 $nav->addFilter('issue/', pht('Setup Issues')); 18 + $nav->addFilter('database/', pht('Database Status')); 18 19 $nav->addFilter('welcome/', pht('Welcome Screen')); 19 20 20 21 return $nav;
+258
src/applications/config/controller/PhabricatorConfigDatabaseController.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigDatabaseController 4 + extends PhabricatorConfigController { 5 + 6 + private $database; 7 + private $table; 8 + 9 + public function willProcessRequest(array $data) { 10 + $this->database = idx($data, 'database'); 11 + $this->table = idx($data, 'table'); 12 + } 13 + 14 + public function processRequest() { 15 + $request = $this->getRequest(); 16 + $viewer = $request->getUser(); 17 + 18 + $conf = PhabricatorEnv::newObjectFromConfig( 19 + 'mysql.configuration-provider', 20 + array($dao = null, 'w')); 21 + 22 + $api = id(new PhabricatorStorageManagementAPI()) 23 + ->setUser($conf->getUser()) 24 + ->setHost($conf->getHost()) 25 + ->setPort($conf->getPort()) 26 + ->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace()) 27 + ->setPassword($conf->getPassword()); 28 + 29 + $query = id(new PhabricatorConfigSchemaQuery()) 30 + ->setAPI($api); 31 + 32 + $actual = $query->loadActualSchema(); 33 + $expect = $query->loadExpectedSchema(); 34 + 35 + if ($this->table) { 36 + return $this->renderTable( 37 + $actual, 38 + $expect, 39 + $this->database, 40 + $this->table); 41 + } else if ($this->database) { 42 + return $this->renderDatabase( 43 + $actual, 44 + $expect, 45 + $this->database); 46 + } else { 47 + return $this->renderServer( 48 + $actual, 49 + $expect); 50 + } 51 + } 52 + 53 + private function buildResponse($title, $body) { 54 + $nav = $this->buildSideNavView(); 55 + $nav->selectFilter('database/'); 56 + 57 + $crumbs = $this->buildApplicationCrumbs(); 58 + if ($this->database) { 59 + $crumbs->addTextCrumb( 60 + pht('Database Status'), 61 + $this->getApplicationURI('database/')); 62 + if ($this->table) { 63 + $crumbs->addTextCrumb( 64 + $this->database, 65 + $this->getApplicationURI('database/'.$this->database.'/')); 66 + $crumbs->addTextCrumb($this->table); 67 + } else { 68 + $crumbs->addTextCrumb($this->database); 69 + } 70 + } else { 71 + $crumbs->addTextCrumb(pht('Database Status')); 72 + } 73 + 74 + $nav->setCrumbs($crumbs); 75 + $nav->appendChild($body); 76 + 77 + return $this->buildApplicationPage( 78 + $nav, 79 + array( 80 + 'title' => $title, 81 + )); 82 + } 83 + 84 + 85 + private function renderServer( 86 + PhabricatorConfigServerSchema $schema, 87 + PhabricatorConfigServerSchema $expect) { 88 + 89 + $icon_ok = id(new PHUIIconView()) 90 + ->setIconFont('fa-check-circle green'); 91 + 92 + $icon_warn = id(new PHUIIconView()) 93 + ->setIconFont('fa-exclamation-circle yellow'); 94 + 95 + $rows = array(); 96 + foreach ($schema->getDatabases() as $database_name => $database) { 97 + 98 + $expect_database = $expect->getDatabase($database_name); 99 + if ($expect_database) { 100 + $expect_set = $expect_database->getCharacterSet(); 101 + $expect_collation = $expect_database->getCollation(); 102 + 103 + if ($database->isSameSchema($expect_database)) { 104 + $icon = $icon_ok; 105 + } else { 106 + $icon = $icon_warn; 107 + } 108 + } else { 109 + $expect_set = null; 110 + $expect_collation = null; 111 + $icon = $icon_warn; 112 + } 113 + 114 + $actual_set = $database->getCharacterSet(); 115 + $actual_collation = $database->getCollation(); 116 + 117 + 118 + 119 + $rows[] = array( 120 + $icon, 121 + phutil_tag( 122 + 'a', 123 + array( 124 + 'href' => $this->getApplicationURI( 125 + '/database/'.$database_name.'/'), 126 + ), 127 + $database_name), 128 + $actual_set, 129 + $expect_set, 130 + $actual_collation, 131 + $expect_collation, 132 + ); 133 + } 134 + 135 + $table = id(new AphrontTableView($rows)) 136 + ->setHeaders( 137 + array( 138 + null, 139 + pht('Database'), 140 + pht('Charset'), 141 + pht('Expected Charset'), 142 + pht('Collation'), 143 + pht('Expected Collation'), 144 + )) 145 + ->setColumnClasses( 146 + array( 147 + '', 148 + 'wide pri', 149 + null, 150 + null, 151 + )); 152 + 153 + $title = pht('Database Status'); 154 + 155 + $box = id(new PHUIObjectBoxView()) 156 + ->setHeaderText($title) 157 + ->appendChild($table); 158 + 159 + return $this->buildResponse($title, $box); 160 + } 161 + 162 + private function renderDatabase( 163 + PhabricatorConfigServerSchema $schema, 164 + PhabricatorConfigServerSchema $expect, 165 + $database_name) { 166 + 167 + $database = $schema->getDatabase($database_name); 168 + if (!$database) { 169 + return new Aphront404Response(); 170 + } 171 + 172 + $rows = array(); 173 + foreach ($database->getTables() as $table_name => $table) { 174 + $rows[] = array( 175 + phutil_tag( 176 + 'a', 177 + array( 178 + 'href' => $this->getApplicationURI( 179 + '/database/'.$database_name.'/'.$table_name.'/'), 180 + ), 181 + $table_name), 182 + $table->getCollation(), 183 + ); 184 + } 185 + 186 + $table = id(new AphrontTableView($rows)) 187 + ->setHeaders( 188 + array( 189 + pht('Table'), 190 + pht('Collation'), 191 + )) 192 + ->setColumnClasses( 193 + array( 194 + 'wide pri', 195 + null, 196 + )); 197 + 198 + $title = pht('Database Status: %s', $database_name); 199 + 200 + $box = id(new PHUIObjectBoxView()) 201 + ->setHeaderText($title) 202 + ->appendChild($table); 203 + 204 + return $this->buildResponse($title, $box); 205 + } 206 + 207 + private function renderTable( 208 + PhabricatorConfigServerSchema $schema, 209 + PhabricatorConfigServerSchema $expect, 210 + $database_name, 211 + $table_name) { 212 + 213 + $database = $schema->getDatabase($database_name); 214 + if (!$database) { 215 + return new Aphront404Response(); 216 + } 217 + 218 + $table = $database->getTable($table_name); 219 + if (!$table) { 220 + return new Aphront404Response(); 221 + } 222 + 223 + $rows = array(); 224 + foreach ($table->getColumns() as $column_name => $column) { 225 + $rows[] = array( 226 + $column_name, 227 + $column->getColumnType(), 228 + $column->getCharacterSet(), 229 + $column->getCollation(), 230 + ); 231 + } 232 + 233 + $table = id(new AphrontTableView($rows)) 234 + ->setHeaders( 235 + array( 236 + pht('Table'), 237 + pht('Column Type'), 238 + pht('Character Set'), 239 + pht('Collation'), 240 + )) 241 + ->setColumnClasses( 242 + array( 243 + 'wide pri', 244 + null, 245 + null, 246 + null 247 + )); 248 + 249 + $title = pht('Database Status: %s.%s', $database_name, $table_name); 250 + 251 + $box = id(new PHUIObjectBoxView()) 252 + ->setHeaderText($title) 253 + ->appendChild($table); 254 + 255 + return $this->buildResponse($title, $box); 256 + } 257 + 258 + }
+46
src/applications/config/schema/PhabricatorConfigColumnSchema.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigColumnSchema extends Phobject { 4 + 5 + private $name; 6 + private $characterSet; 7 + private $collation; 8 + private $columnType; 9 + 10 + public function setColumnType($column_type) { 11 + $this->columnType = $column_type; 12 + return $this; 13 + } 14 + 15 + public function getColumnType() { 16 + return $this->columnType; 17 + } 18 + 19 + public function setCollation($collation) { 20 + $this->collation = $collation; 21 + return $this; 22 + } 23 + 24 + public function getCollation() { 25 + return $this->collation; 26 + } 27 + 28 + public function setCharacterSet($character_set) { 29 + $this->characterSet = $character_set; 30 + return $this; 31 + } 32 + 33 + public function getCharacterSet() { 34 + return $this->characterSet; 35 + } 36 + 37 + public function setName($name) { 38 + $this->name = $name; 39 + return $this; 40 + } 41 + 42 + public function getName() { 43 + return $this->name; 44 + } 45 + 46 + }
+67
src/applications/config/schema/PhabricatorConfigDatabaseSchema.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigDatabaseSchema extends Phobject { 4 + 5 + private $name; 6 + private $characterSet; 7 + private $collation; 8 + private $tables = array(); 9 + 10 + public function addTable(PhabricatorConfigTableSchema $table) { 11 + $key = $table->getName(); 12 + if (isset($this->tables[$key])) { 13 + throw new Exception( 14 + pht('Trying to add duplicate table "%s"!', $key)); 15 + } 16 + $this->tables[$key] = $table; 17 + return $this; 18 + } 19 + 20 + public function getTables() { 21 + return $this->tables; 22 + } 23 + 24 + public function getTable($key) { 25 + return idx($this->tables, $key); 26 + } 27 + 28 + public function isSameSchema(PhabricatorConfigDatabaseSchema $expect) { 29 + return ($this->toDictionary() === $expect->toDictionary()); 30 + } 31 + 32 + public function toDictionary() { 33 + return array( 34 + 'name' => $this->getName(), 35 + 'characterSet' => $this->getCharacterSet(), 36 + 'collation' => $this->getCollation(), 37 + ); 38 + } 39 + 40 + public function setCollation($collation) { 41 + $this->collation = $collation; 42 + return $this; 43 + } 44 + 45 + public function getCollation() { 46 + return $this->collation; 47 + } 48 + 49 + public function setCharacterSet($character_set) { 50 + $this->characterSet = $character_set; 51 + return $this; 52 + } 53 + 54 + public function getCharacterSet() { 55 + return $this->characterSet; 56 + } 57 + 58 + public function setName($name) { 59 + $this->name = $name; 60 + return $this; 61 + } 62 + 63 + public function getName() { 64 + return $this->name; 65 + } 66 + 67 + }
+135
src/applications/config/schema/PhabricatorConfigSchemaQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigSchemaQuery extends Phobject { 4 + 5 + private $api; 6 + 7 + public function setAPI(PhabricatorStorageManagementAPI $api) { 8 + $this->api = $api; 9 + return $this; 10 + } 11 + 12 + protected function getAPI() { 13 + if (!$this->api) { 14 + throw new Exception(pht('Call setAPI() before issuing a query!')); 15 + } 16 + return $this->api; 17 + } 18 + 19 + protected function getConn() { 20 + return $this->getAPI()->getConn(null); 21 + } 22 + 23 + private function getDatabaseNames() { 24 + $api = $this->getAPI(); 25 + $patches = PhabricatorSQLPatchList::buildAllPatches(); 26 + return $api->getDatabaseList( 27 + $patches, 28 + $only_living = true); 29 + } 30 + 31 + public function loadActualSchema() { 32 + $databases = $this->getDatabaseNames(); 33 + 34 + $conn = $this->getConn(); 35 + $tables = queryfx_all( 36 + $conn, 37 + 'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION 38 + FROM INFORMATION_SCHEMA.TABLES 39 + WHERE TABLE_SCHEMA IN (%Ls)', 40 + $databases); 41 + 42 + $database_info = queryfx_all( 43 + $conn, 44 + 'SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME 45 + FROM INFORMATION_SCHEMA.SCHEMATA 46 + WHERE SCHEMA_NAME IN (%Ls)', 47 + $databases); 48 + $database_info = ipull($database_info, null, 'SCHEMA_NAME'); 49 + 50 + $server_schema = new PhabricatorConfigServerSchema(); 51 + 52 + $tables = igroup($tables, 'TABLE_SCHEMA'); 53 + foreach ($tables as $database_name => $database_tables) { 54 + $info = $database_info[$database_name]; 55 + 56 + $database_schema = id(new PhabricatorConfigDatabaseSchema()) 57 + ->setName($database_name) 58 + ->setCharacterSet($info['DEFAULT_CHARACTER_SET_NAME']) 59 + ->setCollation($info['DEFAULT_COLLATION_NAME']); 60 + 61 + foreach ($database_tables as $table) { 62 + $table_name = $table['TABLE_NAME']; 63 + 64 + $table_schema = id(new PhabricatorConfigTableSchema()) 65 + ->setName($table_name) 66 + ->setCollation($table['TABLE_COLLATION']); 67 + 68 + $columns = queryfx_all( 69 + $conn, 70 + 'SELECT COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE 71 + FROM INFORMATION_SCHEMA.COLUMNS 72 + WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s', 73 + $database_name, 74 + $table_name); 75 + 76 + foreach ($columns as $column) { 77 + $column_schema = id(new PhabricatorConfigColumnSchema()) 78 + ->setName($column['COLUMN_NAME']) 79 + ->setCharacterSet($column['CHARACTER_SET_NAME']) 80 + ->setCollation($column['COLLATION_NAME']) 81 + ->setColumnType($column['COLUMN_TYPE']); 82 + 83 + $table_schema->addColumn($column_schema); 84 + } 85 + 86 + $database_schema->addTable($table_schema); 87 + } 88 + 89 + $server_schema->addDatabase($database_schema); 90 + } 91 + 92 + return $server_schema; 93 + } 94 + 95 + 96 + public function loadExpectedSchema() { 97 + $databases = $this->getDatabaseNames(); 98 + 99 + $api = $this->getAPI(); 100 + 101 + if ($api->isCharacterSetAvailable('utf8mb4')) { 102 + // If utf8mb4 is available, we use it with the utf8mb4_unicode_ci 103 + // collation. This is most correct, and will sort properly. 104 + 105 + $utf8_charset = 'utf8mb4'; 106 + $utf8_collate = 'utf8mb4_unicode_ci'; 107 + } else { 108 + // If utf8mb4 is not available, we use binary. This allows us to store 109 + // 4-byte unicode characters. This has some tradeoffs: 110 + // 111 + // Unicode characters won't sort correctly. There's nothing we can do 112 + // about this while still supporting 4-byte characters. 113 + // 114 + // It's possible that strings will be truncated in the middle of a 115 + // character on insert. We encourage users to set STRICT_ALL_TABLES 116 + // to prevent this. 117 + 118 + $utf8_charset = 'binary'; 119 + $utf8_collate = 'binary'; 120 + } 121 + 122 + $server_schema = new PhabricatorConfigServerSchema(); 123 + foreach ($databases as $database_name) { 124 + $database_schema = id(new PhabricatorConfigDatabaseSchema()) 125 + ->setName($database_name) 126 + ->setCharacterSet($utf8_charset) 127 + ->setCollation($utf8_collate); 128 + 129 + $server_schema->addDatabase($database_schema); 130 + } 131 + 132 + return $server_schema; 133 + } 134 + 135 + }
+25
src/applications/config/schema/PhabricatorConfigServerSchema.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigServerSchema extends Phobject { 4 + 5 + private $databases = array(); 6 + 7 + public function addDatabase(PhabricatorConfigDatabaseSchema $database) { 8 + $key = $database->getName(); 9 + if (isset($this->databases[$key])) { 10 + throw new Exception( 11 + pht('Trying to add duplicate database "%s"!', $key)); 12 + } 13 + $this->databases[$key] = $database; 14 + return $this; 15 + } 16 + 17 + public function getDatabases() { 18 + return $this->databases; 19 + } 20 + 21 + public function getDatabase($key) { 22 + return idx($this->getDatabases(), $key); 23 + } 24 + 25 + }
+41
src/applications/config/schema/PhabricatorConfigTableSchema.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigTableSchema extends Phobject { 4 + 5 + private $name; 6 + private $collation; 7 + private $columns = array(); 8 + 9 + public function addColumn(PhabricatorConfigColumnSchema $column) { 10 + $key = $column->getName(); 11 + if (isset($this->columns[$key])) { 12 + throw new Exception( 13 + pht('Trying to add duplicate column "%s"!', $key)); 14 + } 15 + $this->columns[$key] = $column; 16 + return $this; 17 + } 18 + 19 + public function getColumns() { 20 + return $this->columns; 21 + } 22 + 23 + public function setCollation($collation) { 24 + $this->collation = $collation; 25 + return $this; 26 + } 27 + 28 + public function getCollation() { 29 + return $this->collation; 30 + } 31 + 32 + public function setName($name) { 33 + $this->name = $name; 34 + return $this; 35 + } 36 + 37 + public function getName() { 38 + return $this->name; 39 + } 40 + 41 + }
+12
src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
··· 196 196 require_once $script; 197 197 } 198 198 199 + public function isCharacterSetAvailable($character_set) { 200 + $conn = $this->getConn(null); 201 + 202 + $result = queryfx_one( 203 + $conn, 204 + 'SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS 205 + WHERE CHARACTER_SET_NAME = %s', 206 + $character_set); 207 + 208 + return (bool)$result; 209 + } 210 + 199 211 }