Path: blob/master/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
12241 views
<?php12/**3* @task config Configuring Storage4*/5abstract class PhabricatorLiskDAO extends LiskDAO {67private static $namespaceStack = array();8private $forcedNamespace;910const ATTACHABLE = '<attachable>';11const CONFIG_APPLICATION_SERIALIZERS = 'phabricator/serializers';1213/* -( Configuring Storage )------------------------------------------------ */1415/**16* @task config17*/18public static function pushStorageNamespace($namespace) {19self::$namespaceStack[] = $namespace;20}2122/**23* @task config24*/25public static function popStorageNamespace() {26array_pop(self::$namespaceStack);27}2829/**30* @task config31*/32public static function getDefaultStorageNamespace() {33return PhabricatorEnv::getEnvConfig('storage.default-namespace');34}3536/**37* @task config38*/39public static function getStorageNamespace() {40$namespace = end(self::$namespaceStack);41if (!strlen($namespace)) {42$namespace = self::getDefaultStorageNamespace();43}44if ($namespace === null || !strlen($namespace)) {45throw new Exception(pht('No storage namespace configured!'));46}47return $namespace;48}4950public function setForcedStorageNamespace($namespace) {51$this->forcedNamespace = $namespace;52return $this;53}5455/**56* @task config57*/58protected function establishLiveConnection($mode) {59$namespace = self::getStorageNamespace();60$database = $namespace.'_'.$this->getApplicationName();6162$is_readonly = PhabricatorEnv::isReadOnly();6364if ($is_readonly && ($mode != 'r')) {65$this->raiseImproperWrite($database);66}6768$connection = $this->newClusterConnection(69$this->getApplicationName(),70$database,71$mode);7273// TODO: This should be testing if the mode is "r", but that would probably74// break a lot of things. Perform a more narrow test for readonly mode75// until we have greater certainty that this works correctly most of the76// time.77if ($is_readonly) {78$connection->setReadOnly(true);79}8081return $connection;82}8384private function newClusterConnection($application, $database, $mode) {85$master = PhabricatorDatabaseRef::getMasterDatabaseRefForApplication(86$application);8788$master_exception = null;8990if ($master && !$master->isSevered()) {91$connection = $master->newApplicationConnection($database);92if ($master->isReachable($connection)) {93return $connection;94} else {95if ($mode == 'w') {96$this->raiseImpossibleWrite($database);97}98PhabricatorEnv::setReadOnly(99true,100PhabricatorEnv::READONLY_UNREACHABLE);101102$master_exception = $master->getConnectionException();103}104}105106$replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForApplication(107$application);108if ($replica) {109$connection = $replica->newApplicationConnection($database);110$connection->setReadOnly(true);111if ($replica->isReachable($connection)) {112if ($master_exception) {113// If we ended up here as the result of a failover, log the114// exception. This is seriously bad news even if we are able115// to recover from it.116$proxy_exception = new PhutilProxyException(117pht(118'Failed to connect to master database ("%s"), failing over '.119'into read-only mode.',120$database),121$master_exception);122phlog($proxy_exception);123}124125return $connection;126}127}128129if (!$master && !$replica) {130$this->raiseUnconfigured($database);131}132133$this->raiseUnreachable($database, $master_exception);134}135136private function raiseImproperWrite($database) {137throw new PhabricatorClusterImproperWriteException(138pht(139'Unable to establish a write-mode connection (to application '.140'database "%s") because this server is in read-only mode. Whatever '.141'you are trying to do does not function correctly in read-only mode.',142$database));143}144145private function raiseImpossibleWrite($database) {146throw new PhabricatorClusterImpossibleWriteException(147pht(148'Unable to connect to master database ("%s"). This is a severe '.149'failure; your request did not complete.',150$database));151}152153private function raiseUnconfigured($database) {154throw new Exception(155pht(156'Unable to establish a connection to any database host '.157'(while trying "%s"). No masters or replicas are configured.',158$database));159}160161private function raiseUnreachable($database, Exception $proxy = null) {162$message = pht(163'Unable to establish a connection to any database host '.164'(while trying "%s"). All masters and replicas are completely '.165'unreachable.',166$database);167168if ($proxy) {169$proxy_message = pht(170'%s: %s',171get_class($proxy),172$proxy->getMessage());173$message = $message."\n\n".$proxy_message;174}175176throw new PhabricatorClusterStrandedException($message);177}178179180/**181* @task config182*/183public function getTableName() {184$str = 'phabricator';185$len = strlen($str);186187$class = strtolower(get_class($this));188if (!strncmp($class, $str, $len)) {189$class = substr($class, $len);190}191$app = $this->getApplicationName();192if (!strncmp($class, $app, strlen($app))) {193$class = substr($class, strlen($app));194}195196if (strlen($class)) {197return $app.'_'.$class;198} else {199return $app;200}201}202203/**204* @task config205*/206abstract public function getApplicationName();207208protected function getDatabaseName() {209if ($this->forcedNamespace) {210$namespace = $this->forcedNamespace;211} else {212$namespace = self::getStorageNamespace();213}214215return $namespace.'_'.$this->getApplicationName();216}217218/**219* Break a list of escaped SQL statement fragments (e.g., VALUES lists for220* INSERT, previously built with @{function:qsprintf}) into chunks which will221* fit under the MySQL 'max_allowed_packet' limit.222*223* If a statement is too large to fit within the limit, it is broken into224* its own chunk (but might fail when the query executes).225*/226public static function chunkSQL(227array $fragments,228$limit = null) {229230if ($limit === null) {231// NOTE: Hard-code this at 1MB for now, minus a 10% safety buffer.232// Eventually we could query MySQL or let the user configure it.233$limit = (int)((1024 * 1024) * 0.90);234}235236$result = array();237238$chunk = array();239$len = 0;240$glue_len = strlen(', ');241foreach ($fragments as $fragment) {242if ($fragment instanceof PhutilQueryString) {243$this_len = strlen($fragment->getUnmaskedString());244} else {245$this_len = strlen($fragment);246}247248if ($chunk) {249// Chunks after the first also imply glue.250$this_len += $glue_len;251}252253if ($len + $this_len <= $limit) {254$len += $this_len;255$chunk[] = $fragment;256} else {257if ($chunk) {258$result[] = $chunk;259}260$len = ($this_len - $glue_len);261$chunk = array($fragment);262}263}264265if ($chunk) {266$result[] = $chunk;267}268269return $result;270}271272protected function assertAttached($property) {273if ($property === self::ATTACHABLE) {274throw new PhabricatorDataNotAttachedException($this);275}276return $property;277}278279protected function assertAttachedKey($value, $key) {280$this->assertAttached($value);281if (!array_key_exists($key, $value)) {282throw new PhabricatorDataNotAttachedException($this);283}284return $value[$key];285}286287protected function detectEncodingForStorage($string) {288return phutil_is_utf8($string) ? 'utf8' : null;289}290291protected function getUTF8StringFromStorage($string, $encoding) {292if ($encoding == 'utf8') {293return $string;294}295296if (function_exists('mb_detect_encoding')) {297if ($encoding !== null && strlen($encoding)) {298$try_encodings = array(299$encoding,300);301} else {302// TODO: This is pretty much a guess, and probably needs to be303// configurable in the long run.304$try_encodings = array(305'JIS',306'EUC-JP',307'SJIS',308'ISO-8859-1',309);310}311312$guess = mb_detect_encoding($string, $try_encodings);313if ($guess) {314return mb_convert_encoding($string, 'UTF-8', $guess);315}316}317318return phutil_utf8ize($string);319}320321protected function willReadData(array &$data) {322parent::willReadData($data);323324static $custom;325if ($custom === null) {326$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);327}328329if ($custom) {330foreach ($custom as $key => $serializer) {331$data[$key] = $serializer->willReadValue($data[$key]);332}333}334}335336protected function willWriteData(array &$data) {337static $custom;338if ($custom === null) {339$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);340}341342if ($custom) {343foreach ($custom as $key => $serializer) {344$data[$key] = $serializer->willWriteValue($data[$key]);345}346}347348parent::willWriteData($data);349}350351352}353354355