@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.

at recaptime-dev/main 360 lines 9.7 kB view raw
1<?php 2 3/** 4 * @task config Configuring Storage 5 */ 6abstract class PhabricatorLiskDAO extends LiskDAO { 7 8 private static $namespaceStack = array(); 9 private $forcedNamespace; 10 11 const ATTACHABLE = '<attachable>'; 12 const CONFIG_APPLICATION_SERIALIZERS = 'phabricator/serializers'; 13 14/* -( Configuring Storage )------------------------------------------------ */ 15 16 /** 17 * @task config 18 */ 19 public static function pushStorageNamespace($namespace) { 20 self::$namespaceStack[] = $namespace; 21 } 22 23 /** 24 * @task config 25 */ 26 public static function popStorageNamespace() { 27 array_pop(self::$namespaceStack); 28 } 29 30 /** 31 * @task config 32 */ 33 public static function getDefaultStorageNamespace() { 34 return PhabricatorEnv::getEnvConfig('storage.default-namespace'); 35 } 36 37 /** 38 * @task config 39 */ 40 public static function getStorageNamespace() { 41 $namespace = end(self::$namespaceStack); 42 if (!strlen($namespace)) { 43 $namespace = self::getDefaultStorageNamespace(); 44 } 45 if (!strlen($namespace)) { 46 throw new Exception(pht('No storage namespace configured!')); 47 } 48 return $namespace; 49 } 50 51 public function setForcedStorageNamespace($namespace) { 52 $this->forcedNamespace = $namespace; 53 return $this; 54 } 55 56 /** 57 * @task config 58 */ 59 protected function establishLiveConnection($mode) { 60 $namespace = self::getStorageNamespace(); 61 $database = $namespace.'_'.$this->getApplicationName(); 62 63 $is_readonly = PhabricatorEnv::isReadOnly(); 64 65 if ($is_readonly && ($mode != 'r')) { 66 $this->raiseImproperWrite($database); 67 } 68 69 $connection = $this->newClusterConnection( 70 $this->getApplicationName(), 71 $database, 72 $mode); 73 74 // TODO: This should be testing if the mode is "r", but that would probably 75 // break a lot of things. Perform a more narrow test for readonly mode 76 // until we have greater certainty that this works correctly most of the 77 // time. 78 if ($is_readonly) { 79 $connection->setReadOnly(true); 80 } 81 82 return $connection; 83 } 84 85 private function newClusterConnection($application, $database, $mode) { 86 $master = PhabricatorDatabaseRef::getMasterDatabaseRefForApplication( 87 $application); 88 89 $master_exception = null; 90 91 if ($master && !$master->isSevered()) { 92 $connection = $master->newApplicationConnection($database); 93 if ($master->isReachable($connection)) { 94 return $connection; 95 } else { 96 if ($mode == 'w') { 97 $this->raiseImpossibleWrite($database); 98 } 99 PhabricatorEnv::setReadOnly( 100 true, 101 PhabricatorEnv::READONLY_UNREACHABLE); 102 103 $master_exception = $master->getConnectionException(); 104 } 105 } 106 107 $replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForApplication( 108 $application); 109 if ($replica) { 110 $connection = $replica->newApplicationConnection($database); 111 $connection->setReadOnly(true); 112 if ($replica->isReachable($connection)) { 113 if ($master_exception) { 114 // If we ended up here as the result of a failover, log the 115 // exception. This is seriously bad news even if we are able 116 // to recover from it. 117 $proxy_exception = new Exception( 118 pht( 119 'Failed to connect to master database ("%s"), failing over '. 120 'into read-only mode.', 121 $database), 122 0, 123 $master_exception); 124 phlog($proxy_exception); 125 } 126 127 return $connection; 128 } 129 } 130 131 if (!$master && !$replica) { 132 $this->raiseUnconfigured($database); 133 } 134 135 $this->raiseUnreachable($database, $master_exception); 136 } 137 138 private function raiseImproperWrite($database) { 139 throw new PhabricatorClusterImproperWriteException( 140 pht( 141 'Unable to establish a write-mode connection (to application '. 142 'database "%s") because this server is in read-only mode. Whatever '. 143 'you are trying to do does not function correctly in read-only mode.', 144 $database)); 145 } 146 147 private function raiseImpossibleWrite($database) { 148 throw new PhabricatorClusterImpossibleWriteException( 149 pht( 150 'Unable to connect to master database ("%s"). This is a severe '. 151 'failure; your request did not complete.', 152 $database)); 153 } 154 155 private function raiseUnconfigured($database) { 156 throw new Exception( 157 pht( 158 'Unable to establish a connection to any database host '. 159 '(while trying "%s"). No masters or replicas are configured.', 160 $database)); 161 } 162 163 private function raiseUnreachable($database, ?Exception $proxy = null) { 164 $message = pht( 165 'Unable to establish a connection to any database host '. 166 '(while trying "%s"). All masters and replicas are completely '. 167 'unreachable.', 168 $database); 169 170 if ($proxy) { 171 $proxy_message = pht( 172 '%s: %s', 173 get_class($proxy), 174 $proxy->getMessage()); 175 $message = $message."\n\n".$proxy_message; 176 } 177 178 throw new PhabricatorClusterStrandedException($message); 179 } 180 181 182 /** 183 * Get the database table name 184 * @return string Name of the database table 185 * @task config 186 */ 187 public function getTableName() { 188 $str = 'phabricator'; 189 $len = strlen($str); 190 191 $class = strtolower(get_class($this)); 192 if (!strncmp($class, $str, $len)) { 193 $class = substr($class, $len); 194 } 195 $app = $this->getApplicationName(); 196 if (!strncmp($class, $app, strlen($app))) { 197 $class = substr($class, strlen($app)); 198 } 199 200 if (strlen($class)) { 201 return $app.'_'.$class; 202 } else { 203 return $app; 204 } 205 } 206 207 /** 208 * Get the database name 209 * @return string Name of the database 210 * @task config 211 */ 212 abstract public function getApplicationName(); 213 214 protected function getDatabaseName() { 215 if ($this->forcedNamespace) { 216 $namespace = $this->forcedNamespace; 217 } else { 218 $namespace = self::getStorageNamespace(); 219 } 220 221 return $namespace.'_'.$this->getApplicationName(); 222 } 223 224 /** 225 * Break a list of escaped SQL statement fragments (e.g., VALUES lists for 226 * INSERT, previously built with @{function:qsprintf}) into chunks which will 227 * fit under the MySQL 'max_allowed_packet' limit. 228 * 229 * If a statement is too large to fit within the limit, it is broken into 230 * its own chunk (but might fail when the query executes). 231 */ 232 public static function chunkSQL( 233 array $fragments, 234 $limit = null) { 235 236 if ($limit === null) { 237 // NOTE: Hard-code this at 1MB for now, minus a 10% safety buffer. 238 // Eventually we could query MySQL or let the user configure it. 239 $limit = (int)((1024 * 1024) * 0.90); 240 } 241 242 $result = array(); 243 244 $chunk = array(); 245 $len = 0; 246 $glue_len = strlen(', '); 247 foreach ($fragments as $fragment) { 248 if ($fragment instanceof PhutilQueryString) { 249 $this_len = strlen($fragment->getUnmaskedString()); 250 } else { 251 $this_len = strlen($fragment); 252 } 253 254 if ($chunk) { 255 // Chunks after the first also imply glue. 256 $this_len += $glue_len; 257 } 258 259 if ($len + $this_len <= $limit) { 260 $len += $this_len; 261 $chunk[] = $fragment; 262 } else { 263 if ($chunk) { 264 $result[] = $chunk; 265 } 266 $len = ($this_len - $glue_len); 267 $chunk = array($fragment); 268 } 269 } 270 271 if ($chunk) { 272 $result[] = $chunk; 273 } 274 275 return $result; 276 } 277 278 protected function assertAttached($property) { 279 if ($property === self::ATTACHABLE) { 280 throw new PhabricatorDataNotAttachedException($this); 281 } 282 return $property; 283 } 284 285 protected function assertAttachedKey($value, $key) { 286 $this->assertAttached($value); 287 if (!array_key_exists($key, $value)) { 288 throw new PhabricatorDataNotAttachedException($this); 289 } 290 return $value[$key]; 291 } 292 293 protected function detectEncodingForStorage($string) { 294 return phutil_is_utf8($string) ? 'utf8' : null; 295 } 296 297 protected function getUTF8StringFromStorage($string, $encoding) { 298 if ($encoding == 'utf8' || !phutil_nonempty_string($string)) { 299 return $string; 300 } 301 302 if (function_exists('mb_detect_encoding')) { 303 if (phutil_nonempty_string($encoding)) { 304 $try_encodings = array( 305 $encoding, 306 ); 307 } else { 308 // TODO: This is pretty much a guess, and probably needs to be 309 // configurable in the long run. 310 $try_encodings = array( 311 'JIS', 312 'EUC-JP', 313 'SJIS', 314 'ISO-8859-1', 315 ); 316 } 317 318 $guess = mb_detect_encoding($string, $try_encodings); 319 if ($guess) { 320 return mb_convert_encoding($string, 'UTF-8', $guess); 321 } 322 } 323 324 return phutil_utf8ize($string); 325 } 326 327 protected function willReadData(array &$data) { 328 parent::willReadData($data); 329 330 static $custom = array(); 331 if (!isset($custom[static::class])) { 332 $custom[static::class] = $this->getConfigOption( 333 self::CONFIG_APPLICATION_SERIALIZERS); 334 } 335 336 if (!empty($custom[static::class])) { 337 foreach ($custom[static::class] as $key => $serializer) { 338 $data[$key] = $serializer->willReadValue($data[$key]); 339 } 340 } 341 } 342 343 protected function willWriteData(array &$data) { 344 static $custom = array(); 345 if (!isset($custom[static::class])) { 346 $custom[static::class] = $this->getConfigOption( 347 self::CONFIG_APPLICATION_SERIALIZERS); 348 } 349 350 if (!empty($custom[static::class])) { 351 foreach ($custom[static::class] as $key => $serializer) { 352 $data[$key] = $serializer->willWriteValue($data[$key]); 353 } 354 } 355 356 parent::willWriteData($data); 357 } 358 359 360}