mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 02:02:41 +01:00
b20884a842
Summary: Ref T5179. Ref T4045. Ref T832. We can now write non-utf8 hunks into the database, so try to do more reasonable things with them in the UI. Test Plan: (See screenshots...) Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T832, T4045, T5179 Differential Revision: https://secure.phabricator.com/D9294
222 lines
5.3 KiB
PHP
222 lines
5.3 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @task config Configuring Storage
|
|
*/
|
|
abstract class PhabricatorLiskDAO extends LiskDAO {
|
|
|
|
private static $namespaceStack = array();
|
|
|
|
const ATTACHABLE = '<attachable>';
|
|
|
|
/* -( Configuring Storage )------------------------------------------------ */
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
public static function pushStorageNamespace($namespace) {
|
|
self::$namespaceStack[] = $namespace;
|
|
}
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
public static function popStorageNamespace() {
|
|
array_pop(self::$namespaceStack);
|
|
}
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
public static function getDefaultStorageNamespace() {
|
|
return PhabricatorEnv::getEnvConfig('storage.default-namespace');
|
|
}
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
public static function getStorageNamespace() {
|
|
$namespace = end(self::$namespaceStack);
|
|
if (!strlen($namespace)) {
|
|
$namespace = self::getDefaultStorageNamespace();
|
|
}
|
|
if (!strlen($namespace)) {
|
|
throw new Exception('No storage namespace configured!');
|
|
}
|
|
return $namespace;
|
|
}
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
public function establishLiveConnection($mode) {
|
|
$namespace = self::getStorageNamespace();
|
|
|
|
$conf = PhabricatorEnv::newObjectFromConfig(
|
|
'mysql.configuration-provider',
|
|
array($this, $mode, $namespace));
|
|
|
|
return PhabricatorEnv::newObjectFromConfig(
|
|
'mysql.implementation',
|
|
array(
|
|
array(
|
|
'user' => $conf->getUser(),
|
|
'pass' => $conf->getPassword(),
|
|
'host' => $conf->getHost(),
|
|
'port' => $conf->getPort(),
|
|
'database' => $conf->getDatabase(),
|
|
'retries' => 3,
|
|
),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
public function getTableName() {
|
|
$str = 'phabricator';
|
|
$len = strlen($str);
|
|
|
|
$class = strtolower(get_class($this));
|
|
if (!strncmp($class, $str, $len)) {
|
|
$class = substr($class, $len);
|
|
}
|
|
$app = $this->getApplicationName();
|
|
if (!strncmp($class, $app, strlen($app))) {
|
|
$class = substr($class, strlen($app));
|
|
}
|
|
|
|
if (strlen($class)) {
|
|
return $app.'_'.$class;
|
|
} else {
|
|
return $app;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @task config
|
|
*/
|
|
abstract public function getApplicationName();
|
|
|
|
protected function getConnectionNamespace() {
|
|
return self::getStorageNamespace().'_'.$this->getApplicationName();
|
|
}
|
|
|
|
|
|
/**
|
|
* Break a list of escaped SQL statement fragments (e.g., VALUES lists for
|
|
* INSERT, previously built with @{function:qsprintf}) into chunks which will
|
|
* fit under the MySQL 'max_allowed_packet' limit.
|
|
*
|
|
* Chunks are glued together with `$glue`, by default ", ".
|
|
*
|
|
* If a statement is too large to fit within the limit, it is broken into
|
|
* its own chunk (but might fail when the query executes).
|
|
*/
|
|
public static function chunkSQL(
|
|
array $fragments,
|
|
$glue = ', ',
|
|
$limit = null) {
|
|
|
|
if ($limit === null) {
|
|
// NOTE: Hard-code this at 1MB for now, minus a 10% safety buffer.
|
|
// Eventually we could query MySQL or let the user configure it.
|
|
$limit = (int)((1024 * 1024) * 0.90);
|
|
}
|
|
|
|
$result = array();
|
|
|
|
$chunk = array();
|
|
$len = 0;
|
|
$glue_len = strlen($glue);
|
|
foreach ($fragments as $fragment) {
|
|
$this_len = strlen($fragment);
|
|
|
|
if ($chunk) {
|
|
// Chunks after the first also imply glue.
|
|
$this_len += $glue_len;
|
|
}
|
|
|
|
if ($len + $this_len <= $limit) {
|
|
$len += $this_len;
|
|
$chunk[] = $fragment;
|
|
} else {
|
|
if ($chunk) {
|
|
$result[] = $chunk;
|
|
}
|
|
$len = strlen($fragment);
|
|
$chunk = array($fragment);
|
|
}
|
|
}
|
|
|
|
if ($chunk) {
|
|
$result[] = $chunk;
|
|
}
|
|
|
|
foreach ($result as $key => $fragment_list) {
|
|
$result[$key] = implode($glue, $fragment_list);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function assertAttached($property) {
|
|
if ($property === self::ATTACHABLE) {
|
|
throw new PhabricatorDataNotAttachedException($this);
|
|
}
|
|
return $property;
|
|
}
|
|
|
|
protected function assertAttachedKey($value, $key) {
|
|
$this->assertAttached($value);
|
|
if (!array_key_exists($key, $value)) {
|
|
throw new PhabricatorDataNotAttachedException($this);
|
|
}
|
|
return $value[$key];
|
|
}
|
|
|
|
protected function detectEncodingForStorage($string) {
|
|
return phutil_is_utf8($string) ? 'utf8' : null;
|
|
}
|
|
|
|
protected function getUTF8StringFromStorage($string, $encoding) {
|
|
if ($encoding == 'utf8') {
|
|
return $string;
|
|
}
|
|
|
|
if (function_exists('mb_detect_encoding')) {
|
|
if (strlen($encoding)) {
|
|
$try_encodings = array(
|
|
$encoding,
|
|
);
|
|
} else {
|
|
// TODO: This is pretty much a guess, and probably needs to be
|
|
// configurable in the long run.
|
|
$try_encodings = array(
|
|
'JIS',
|
|
'EUC-JP',
|
|
'SJIS',
|
|
'ISO-8859-1',
|
|
);
|
|
}
|
|
|
|
$guess = mb_detect_encoding($string, $try_encodings);
|
|
if ($guess) {
|
|
return mb_convert_encoding($string, 'UTF-8', $guess);
|
|
}
|
|
}
|
|
|
|
return phutil_utf8ize($string);
|
|
}
|
|
|
|
public function delete() {
|
|
|
|
// TODO: We should make some reasonable effort to destroy related
|
|
// infrastructure objects here, like edges, transactions, custom field
|
|
// storage, flags, Phrequent tracking, tokens, etc. This doesn't need to
|
|
// be exhaustive, but we can get a lot of it pretty easily.
|
|
|
|
return parent::delete();
|
|
}
|
|
|
|
}
|