Path: blob/master/src/applications/config/schema/PhabricatorConfigSchemaSpec.php
12256 views
<?php12abstract class PhabricatorConfigSchemaSpec extends Phobject {34private $server;5private $utf8Charset;6private $utf8BinaryCollation;7private $utf8SortingCollation;89const DATATYPE_UNKNOWN = '<unknown>';1011public function setUTF8SortingCollation($utf8_sorting_collation) {12$this->utf8SortingCollation = $utf8_sorting_collation;13return $this;14}1516public function getUTF8SortingCollation() {17return $this->utf8SortingCollation;18}1920public function setUTF8BinaryCollation($utf8_binary_collation) {21$this->utf8BinaryCollation = $utf8_binary_collation;22return $this;23}2425public function getUTF8BinaryCollation() {26return $this->utf8BinaryCollation;27}2829public function setUTF8Charset($utf8_charset) {30$this->utf8Charset = $utf8_charset;31return $this;32}3334public function getUTF8Charset() {35return $this->utf8Charset;36}3738public function setServer(PhabricatorConfigServerSchema $server) {39$this->server = $server;40return $this;41}4243public function getServer() {44return $this->server;45}4647abstract public function buildSchemata();4849protected function buildLiskObjectSchema(PhabricatorLiskDAO $object) {50$index_options = array();5152$persistence = $object->getSchemaPersistence();53if ($persistence !== null) {54$index_options['persistence'] = $persistence;55}5657$this->buildRawSchema(58$object->getApplicationName(),59$object->getTableName(),60$object->getSchemaColumns(),61$object->getSchemaKeys(),62$index_options);63}6465protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) {66$index_options = array(67'persistence' => PhabricatorConfigTableSchema::PERSISTENCE_INDEX,68);6970$this->buildRawSchema(71$engine->getApplicationName(),72$engine->getDocumentTableName(),73$engine->getDocumentSchemaColumns(),74$engine->getDocumentSchemaKeys(),75$index_options);7677$this->buildRawSchema(78$engine->getApplicationName(),79$engine->getFieldTableName(),80$engine->getFieldSchemaColumns(),81$engine->getFieldSchemaKeys(),82$index_options);8384$this->buildRawSchema(85$engine->getApplicationName(),86$engine->getNgramsTableName(),87$engine->getNgramsSchemaColumns(),88$engine->getNgramsSchemaKeys(),89$index_options);9091// NOTE: The common ngrams table is not marked as an index table. It is92// tiny and persisting it across a restore saves us a lot of work garbage93// collecting common ngrams from the index after it gets built.9495$this->buildRawSchema(96$engine->getApplicationName(),97$engine->getCommonNgramsTableName(),98$engine->getCommonNgramsSchemaColumns(),99$engine->getCommonNgramsSchemaKeys());100}101102protected function buildRawSchema(103$database_name,104$table_name,105array $columns,106array $keys,107array $options = array()) {108109PhutilTypeSpec::checkMap(110$options,111array(112'persistence' => 'optional string',113));114115$database = $this->getDatabase($database_name);116117$table = $this->newTable($table_name);118119if (PhabricatorSearchDocument::isInnoDBFulltextEngineAvailable()) {120$fulltext_engine = 'InnoDB';121} else {122$fulltext_engine = 'MyISAM';123}124125foreach ($columns as $name => $type) {126if ($type === null) {127continue;128}129130$details = $this->getDetailsForDataType($type);131132$column_type = $details['type'];133$charset = $details['charset'];134$collation = $details['collation'];135$nullable = $details['nullable'];136$auto = $details['auto'];137138$column = $this->newColumn($name)139->setDataType($type)140->setColumnType($column_type)141->setCharacterSet($charset)142->setCollation($collation)143->setNullable($nullable)144->setAutoIncrement($auto);145146// If this table has any FULLTEXT fields, we expect it to use the best147// available FULLTEXT engine, which may not be InnoDB.148switch ($type) {149case 'fulltext':150case 'fulltext?':151$table->setEngine($fulltext_engine);152break;153}154155$table->addColumn($column);156}157158foreach ($keys as $key_name => $key_spec) {159if ($key_spec === null) {160// This is a subclass removing a key which Lisk expects.161continue;162}163164$key = $this->newKey($key_name)165->setColumnNames(idx($key_spec, 'columns', array()));166167$key->setUnique((bool)idx($key_spec, 'unique'));168$key->setIndexType(idx($key_spec, 'type', 'BTREE'));169170$table->addKey($key);171}172173$persistence_type = idx($options, 'persistence');174if ($persistence_type !== null) {175$table->setPersistenceType($persistence_type);176}177178$database->addTable($table);179}180181protected function buildEdgeSchemata(PhabricatorLiskDAO $object) {182$this->buildRawSchema(183$object->getApplicationName(),184PhabricatorEdgeConfig::TABLE_NAME_EDGE,185array(186'src' => 'phid',187'type' => 'uint32',188'dst' => 'phid',189'dateCreated' => 'epoch',190'seq' => 'uint32',191'dataID' => 'id?',192),193array(194'PRIMARY' => array(195'columns' => array('src', 'type', 'dst'),196'unique' => true,197),198'src' => array(199'columns' => array('src', 'type', 'dateCreated', 'seq'),200),201'key_dst' => array(202'columns' => array('dst', 'type', 'src'),203'unique' => true,204),205));206207$this->buildRawSchema(208$object->getApplicationName(),209PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA,210array(211'id' => 'auto',212'data' => 'text',213),214array(215'PRIMARY' => array(216'columns' => array('id'),217'unique' => true,218),219));220}221222protected function getDatabase($name) {223$server = $this->getServer();224225$database = $server->getDatabase($this->getNamespacedDatabase($name));226if (!$database) {227$database = $this->newDatabase($name);228$server->addDatabase($database);229}230231return $database;232}233234protected function newDatabase($name) {235return id(new PhabricatorConfigDatabaseSchema())236->setName($this->getNamespacedDatabase($name))237->setCharacterSet($this->getUTF8Charset())238->setCollation($this->getUTF8BinaryCollation());239}240241protected function getNamespacedDatabase($name) {242$namespace = PhabricatorLiskDAO::getStorageNamespace();243return $namespace.'_'.$name;244}245246protected function newTable($name) {247return id(new PhabricatorConfigTableSchema())248->setName($name)249->setCollation($this->getUTF8BinaryCollation())250->setEngine('InnoDB');251}252253protected function newColumn($name) {254return id(new PhabricatorConfigColumnSchema())255->setName($name);256}257258protected function newKey($name) {259return id(new PhabricatorConfigKeySchema())260->setName($name);261}262263public function getMaximumByteLengthForDataType($data_type) {264$info = $this->getDetailsForDataType($data_type);265return idx($info, 'bytes');266}267268private function getDetailsForDataType($data_type) {269$column_type = null;270$charset = null;271$collation = null;272$auto = false;273$bytes = null;274275// If the type ends with "?", make the column nullable.276$nullable = false;277if (preg_match('/\?$/', $data_type)) {278$nullable = true;279$data_type = substr($data_type, 0, -1);280}281282// NOTE: MySQL allows fragments like "VARCHAR(32) CHARACTER SET binary",283// but just interprets that to mean "VARBINARY(32)". The fragment is284// totally disallowed in a MODIFY statement vs a CREATE TABLE statement.285286$is_binary = ($this->getUTF8Charset() == 'binary');287$matches = null;288$pattern = '/^(fulltext|sort|text|char)(\d+)?\z/';289if (preg_match($pattern, $data_type, $matches)) {290291// Limit the permitted column lengths under the theory that it would292// be nice to eventually reduce this to a small set of standard lengths.293294static $valid_types = array(295'text255' => true,296'text160' => true,297'text128' => true,298'text64' => true,299'text40' => true,300'text32' => true,301'text20' => true,302'text16' => true,303'text12' => true,304'text8' => true,305'text4' => true,306'text' => true,307'char3' => true,308'sort255' => true,309'sort128' => true,310'sort64' => true,311'sort32' => true,312'sort' => true,313'fulltext' => true,314);315316if (empty($valid_types[$data_type])) {317throw new Exception(pht('Unknown column type "%s"!', $data_type));318}319320$type = $matches[1];321$size = idx($matches, 2);322323if ($size) {324$bytes = $size;325}326327switch ($type) {328case 'text':329if ($is_binary) {330if ($size) {331$column_type = 'varbinary('.$size.')';332} else {333$column_type = 'longblob';334}335} else {336if ($size) {337$column_type = 'varchar('.$size.')';338} else {339$column_type = 'longtext';340}341}342break;343case 'sort':344if ($size) {345$column_type = 'varchar('.$size.')';346} else {347$column_type = 'longtext';348}349break;350case 'fulltext';351// MySQL (at least, under MyISAM) refuses to create a FULLTEXT index352// on a LONGBLOB column. We'd also lose case insensitivity in search.353// Force this column to utf8 collation. This will truncate results354// with 4-byte UTF characters in their text, but work reasonably in355// the majority of cases.356$column_type = 'longtext';357break;358case 'char':359$column_type = 'char('.$size.')';360break;361}362363switch ($type) {364case 'text':365case 'char':366if ($is_binary) {367// We leave collation and character set unspecified in order to368// generate valid SQL.369} else {370$charset = $this->getUTF8Charset();371$collation = $this->getUTF8BinaryCollation();372}373break;374case 'sort':375case 'fulltext':376if ($is_binary) {377$charset = 'utf8';378} else {379$charset = $this->getUTF8Charset();380}381$collation = $this->getUTF8SortingCollation();382break;383}384} else {385switch ($data_type) {386case 'auto':387$column_type = 'int(10) unsigned';388$auto = true;389break;390case 'auto64':391$column_type = 'bigint(20) unsigned';392$auto = true;393break;394case 'id':395case 'epoch':396case 'uint32':397$column_type = 'int(10) unsigned';398break;399case 'sint32':400$column_type = 'int(10)';401break;402case 'id64':403case 'uint64':404$column_type = 'bigint(20) unsigned';405break;406case 'sint64':407$column_type = 'bigint(20)';408break;409case 'phid':410case 'policy';411case 'hashpath64':412case 'ipaddress':413$column_type = 'varbinary(64)';414break;415case 'bytes64':416$column_type = 'binary(64)';417break;418case 'bytes40':419$column_type = 'binary(40)';420break;421case 'bytes32':422$column_type = 'binary(32)';423break;424case 'bytes20':425$column_type = 'binary(20)';426break;427case 'bytes12':428$column_type = 'binary(12)';429break;430case 'bytes4':431$column_type = 'binary(4)';432break;433case 'bytes':434$column_type = 'longblob';435break;436case 'bool':437$column_type = 'tinyint(1)';438break;439case 'double':440$column_type = 'double';441break;442case 'date':443$column_type = 'date';444break;445default:446$column_type = self::DATATYPE_UNKNOWN;447$charset = self::DATATYPE_UNKNOWN;448$collation = self::DATATYPE_UNKNOWN;449break;450}451}452453return array(454'type' => $column_type,455'charset' => $charset,456'collation' => $collation,457'nullable' => $nullable,458'auto' => $auto,459'bytes' => $bytes,460);461}462463}464465466