Path: blob/master/src/applications/config/schema/PhabricatorConfigSchemaQuery.php
12256 views
<?php12final class PhabricatorConfigSchemaQuery extends Phobject {34private $refs;5private $apis;67public function setRefs(array $refs) {8$this->refs = $refs;9return $this;10}1112public function getRefs() {13if (!$this->refs) {14return PhabricatorDatabaseRef::getMasterDatabaseRefs();15}16return $this->refs;17}1819public function setAPIs(array $apis) {20$map = array();21foreach ($apis as $api) {22$map[$api->getRef()->getRefKey()] = $api;23}24$this->apis = $map;25return $this;26}2728private function getDatabaseNames(PhabricatorDatabaseRef $ref) {29$api = $this->getAPI($ref);30$patches = PhabricatorSQLPatchList::buildAllPatches();31return $api->getDatabaseList(32$patches,33$only_living = true);34}3536private function getAPI(PhabricatorDatabaseRef $ref) {37$key = $ref->getRefKey();3839if (isset($this->apis[$key])) {40return $this->apis[$key];41}4243return id(new PhabricatorStorageManagementAPI())44->setUser($ref->getUser())45->setHost($ref->getHost())46->setPort($ref->getPort())47->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace())48->setPassword($ref->getPass());49}5051public function loadActualSchemata() {52$refs = $this->getRefs();5354$schemata = array();55foreach ($refs as $ref) {56$schema = $this->loadActualSchemaForServer($ref);57$schemata[$schema->getRef()->getRefKey()] = $schema;58}5960return $schemata;61}6263private function loadActualSchemaForServer(PhabricatorDatabaseRef $ref) {64$databases = $this->getDatabaseNames($ref);6566$conn = $ref->newManagementConnection();6768$tables = queryfx_all(69$conn,70'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION, ENGINE71FROM INFORMATION_SCHEMA.TABLES72WHERE TABLE_SCHEMA IN (%Ls)',73$databases);7475$database_info = queryfx_all(76$conn,77'SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME78FROM INFORMATION_SCHEMA.SCHEMATA79WHERE SCHEMA_NAME IN (%Ls)',80$databases);81$database_info = ipull($database_info, null, 'SCHEMA_NAME');8283// Find databases which exist, but which the user does not have permission84// to see.85$invisible_databases = array();86foreach ($databases as $database_name) {87if (isset($database_info[$database_name])) {88continue;89}9091try {92queryfx($conn, 'SHOW TABLES IN %T', $database_name);93} catch (AphrontAccessDeniedQueryException $ex) {94// This database exists, the user just doesn't have permission to95// see it.96$invisible_databases[] = $database_name;97} catch (AphrontSchemaQueryException $ex) {98// This database is legitimately missing.99}100}101102$sql = array();103foreach ($tables as $table) {104$sql[] = qsprintf(105$conn,106'(TABLE_SCHEMA = %s AND TABLE_NAME = %s)',107$table['TABLE_SCHEMA'],108$table['TABLE_NAME']);109}110111if ($sql) {112$column_info = queryfx_all(113$conn,114'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME,115COLLATION_NAME, COLUMN_TYPE, IS_NULLABLE, EXTRA116FROM INFORMATION_SCHEMA.COLUMNS117WHERE %LO',118$sql);119$column_info = igroup($column_info, 'TABLE_SCHEMA');120} else {121$column_info = array();122}123124// NOTE: Tables like KEY_COLUMN_USAGE and TABLE_CONSTRAINTS only contain125// primary, unique, and foreign keys, so we can't use them here. We pull126// indexes later on using SHOW INDEXES.127128$server_schema = id(new PhabricatorConfigServerSchema())129->setRef($ref);130131$tables = igroup($tables, 'TABLE_SCHEMA');132foreach ($tables as $database_name => $database_tables) {133$info = $database_info[$database_name];134135$database_schema = id(new PhabricatorConfigDatabaseSchema())136->setName($database_name)137->setCharacterSet($info['DEFAULT_CHARACTER_SET_NAME'])138->setCollation($info['DEFAULT_COLLATION_NAME']);139140$database_column_info = idx($column_info, $database_name, array());141$database_column_info = igroup($database_column_info, 'TABLE_NAME');142143foreach ($database_tables as $table) {144$table_name = $table['TABLE_NAME'];145146$table_schema = id(new PhabricatorConfigTableSchema())147->setName($table_name)148->setCollation($table['TABLE_COLLATION'])149->setEngine($table['ENGINE']);150151$columns = idx($database_column_info, $table_name, array());152foreach ($columns as $column) {153if (strpos($column['EXTRA'], 'auto_increment') === false) {154$auto_increment = false;155} else {156$auto_increment = true;157}158159$column_schema = id(new PhabricatorConfigColumnSchema())160->setName($column['COLUMN_NAME'])161->setCharacterSet($column['CHARACTER_SET_NAME'])162->setCollation($column['COLLATION_NAME'])163->setColumnType($column['COLUMN_TYPE'])164->setNullable($column['IS_NULLABLE'] == 'YES')165->setAutoIncrement($auto_increment);166167$table_schema->addColumn($column_schema);168}169170$key_parts = queryfx_all(171$conn,172'SHOW INDEXES FROM %T.%T',173$database_name,174$table_name);175$keys = igroup($key_parts, 'Key_name');176foreach ($keys as $key_name => $key_pieces) {177$key_pieces = isort($key_pieces, 'Seq_in_index');178$head = head($key_pieces);179180// This handles string indexes which index only a prefix of a field.181$column_names = array();182foreach ($key_pieces as $piece) {183$name = $piece['Column_name'];184if ($piece['Sub_part']) {185$name = $name.'('.$piece['Sub_part'].')';186}187$column_names[] = $name;188}189190$key_schema = id(new PhabricatorConfigKeySchema())191->setName($key_name)192->setColumnNames($column_names)193->setUnique(!$head['Non_unique'])194->setIndexType($head['Index_type']);195196$table_schema->addKey($key_schema);197}198199$database_schema->addTable($table_schema);200}201202$server_schema->addDatabase($database_schema);203}204205foreach ($invisible_databases as $database_name) {206$server_schema->addDatabase(207id(new PhabricatorConfigDatabaseSchema())208->setName($database_name)209->setAccessDenied(true));210}211212return $server_schema;213}214215public function loadExpectedSchemata() {216$refs = $this->getRefs();217218$schemata = array();219foreach ($refs as $ref) {220$schema = $this->loadExpectedSchemaForServer($ref);221$schemata[$schema->getRef()->getRefKey()] = $schema;222}223224return $schemata;225}226227public function loadExpectedSchemaForServer(PhabricatorDatabaseRef $ref) {228$databases = $this->getDatabaseNames($ref);229$info = $this->getAPI($ref)->getCharsetInfo();230231$specs = id(new PhutilClassMapQuery())232->setAncestorClass('PhabricatorConfigSchemaSpec')233->execute();234235$server_schema = id(new PhabricatorConfigServerSchema())236->setRef($ref);237238foreach ($specs as $spec) {239$spec240->setUTF8Charset(241$info[PhabricatorStorageManagementAPI::CHARSET_DEFAULT])242->setUTF8BinaryCollation(243$info[PhabricatorStorageManagementAPI::COLLATE_TEXT])244->setUTF8SortingCollation(245$info[PhabricatorStorageManagementAPI::COLLATE_SORT])246->setServer($server_schema)247->buildSchemata($server_schema);248}249250return $server_schema;251}252253public function buildComparisonSchemata(254array $expect_servers,255array $actual_servers) {256257$schemata = array();258foreach ($actual_servers as $key => $actual_server) {259$schemata[$key] = $this->buildComparisonSchemaForServer(260$expect_servers[$key],261$actual_server);262}263264return $schemata;265}266267private function buildComparisonSchemaForServer(268PhabricatorConfigServerSchema $expect,269PhabricatorConfigServerSchema $actual) {270271$comp_server = $actual->newEmptyClone();272273$all_databases = $actual->getDatabases() + $expect->getDatabases();274foreach ($all_databases as $database_name => $database_template) {275$actual_database = $actual->getDatabase($database_name);276$expect_database = $expect->getDatabase($database_name);277278$issues = $this->compareSchemata($expect_database, $actual_database);279280$comp_database = $database_template->newEmptyClone()281->setIssues($issues);282283if (!$actual_database) {284$actual_database = $expect_database->newEmptyClone();285}286287if (!$expect_database) {288$expect_database = $actual_database->newEmptyClone();289}290291$all_tables =292$actual_database->getTables() +293$expect_database->getTables();294foreach ($all_tables as $table_name => $table_template) {295$actual_table = $actual_database->getTable($table_name);296$expect_table = $expect_database->getTable($table_name);297298$issues = $this->compareSchemata($expect_table, $actual_table);299300$comp_table = $table_template->newEmptyClone()301->setIssues($issues);302303if (!$actual_table) {304$actual_table = $expect_table->newEmptyClone();305}306if (!$expect_table) {307$expect_table = $actual_table->newEmptyClone();308}309310$all_columns =311$actual_table->getColumns() +312$expect_table->getColumns();313foreach ($all_columns as $column_name => $column_template) {314$actual_column = $actual_table->getColumn($column_name);315$expect_column = $expect_table->getColumn($column_name);316317$issues = $this->compareSchemata($expect_column, $actual_column);318319$comp_column = $column_template->newEmptyClone()320->setIssues($issues);321322$comp_table->addColumn($comp_column);323}324325$all_keys =326$actual_table->getKeys() +327$expect_table->getKeys();328foreach ($all_keys as $key_name => $key_template) {329$actual_key = $actual_table->getKey($key_name);330$expect_key = $expect_table->getKey($key_name);331332$issues = $this->compareSchemata($expect_key, $actual_key);333334$comp_key = $key_template->newEmptyClone()335->setIssues($issues);336337$comp_table->addKey($comp_key);338}339340$comp_table->setPersistenceType($expect_table->getPersistenceType());341342$comp_database->addTable($comp_table);343}344$comp_server->addDatabase($comp_database);345}346347return $comp_server;348}349350private function compareSchemata(351PhabricatorConfigStorageSchema $expect = null,352PhabricatorConfigStorageSchema $actual = null) {353354$expect_is_key = ($expect instanceof PhabricatorConfigKeySchema);355$actual_is_key = ($actual instanceof PhabricatorConfigKeySchema);356357if ($expect_is_key || $actual_is_key) {358$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY;359$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY;360} else {361$missing_issue = PhabricatorConfigStorageSchema::ISSUE_MISSING;362$surplus_issue = PhabricatorConfigStorageSchema::ISSUE_SURPLUS;363}364365if (!$expect && !$actual) {366throw new Exception(pht('Can not compare two missing schemata!'));367} else if ($expect && !$actual) {368$issues = array($missing_issue);369} else if ($actual && !$expect) {370$issues = array($surplus_issue);371} else {372$issues = $actual->compareTo($expect);373}374375return $issues;376}377378379}380381382