Page MenuHomestyx hydra

No OneTemporary

diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php
index d1e1d16b6f..82ba9c9333 100755
--- a/scripts/sql/manage_storage.php
+++ b/scripts/sql/manage_storage.php
@@ -1,172 +1,178 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('manage Phabricator storage and schemata'));
$args->setSynopsis(<<<EOHELP
**storage** __workflow__ [__options__]
Manage Phabricator database storage and schema versioning.
**storage** upgrade
Initialize or upgrade Phabricator storage.
**storage** upgrade --user __root__ --password __hunter2__
Use administrative credentials for schema changes.
EOHELP
);
$args->parseStandardArguments();
-$conf = PhabricatorEnv::newObjectFromConfig(
- 'mysql.configuration-provider',
- array($dao = null, 'w'));
-
-$default_user = $conf->getUser();
-$default_host = $conf->getHost();
-$default_port = $conf->getPort();
$default_namespace = PhabricatorLiskDAO::getDefaultStorageNamespace();
try {
$args->parsePartial(
array(
array(
'name' => 'force',
'short' => 'f',
'help' => pht(
'Do not prompt before performing dangerous operations.'),
),
array(
'name' => 'user',
'short' => 'u',
'param' => 'username',
- 'default' => $default_user,
'help' => pht(
- "Connect with __username__ instead of the configured default ('%s').",
- $default_user),
+ 'Connect with __username__ instead of the configured default.'),
),
array(
'name' => 'password',
'short' => 'p',
'param' => 'password',
'help' => pht('Use __password__ instead of the configured default.'),
),
array(
'name' => 'namespace',
'param' => 'name',
'default' => $default_namespace,
'help' => pht(
"Use namespace __namespace__ instead of the configured ".
"default ('%s'). This is an advanced feature used by unit tests; ".
"you should not normally use this flag.",
$default_namespace),
),
array(
'name' => 'dryrun',
'help' => pht(
'Do not actually change anything, just show what would be changed.'),
),
array(
'name' => 'disable-utf8mb4',
'help' => pht(
'Disable %s, even if the database supports it. This is an '.
'advanced feature used for testing changes to Phabricator; you '.
'should not normally use this flag.',
'utf8mb4'),
),
));
} catch (PhutilArgumentUsageException $ex) {
$args->printUsageException($ex);
exit(77);
}
// First, test that the Phabricator configuration is set up correctly. After
// we know this works we'll test any administrative credentials specifically.
+$ref = PhabricatorDatabaseRef::getMasterDatabaseRef();
+if (!$ref) {
+ throw new Exception(
+ pht('No database master is configured.'));
+}
+
+$default_user = $ref->getUser();
+$default_host = $ref->getHost();
+$default_port = $ref->getPort();
+
$test_api = id(new PhabricatorStorageManagementAPI())
->setUser($default_user)
->setHost($default_host)
->setPort($default_port)
- ->setPassword($conf->getPassword())
+ ->setPassword($ref->getPass())
->setNamespace($args->getArg('namespace'));
try {
queryfx(
$test_api->getConn(null),
'SELECT 1');
} catch (AphrontQueryException $ex) {
$message = phutil_console_format(
"**%s**\n\n%s\n\n%s\n\n%s\n\n**%s**: %s\n",
pht('MySQL Credentials Not Configured'),
pht(
'Unable to connect to MySQL using the configured credentials. '.
'You must configure standard credentials before you can upgrade '.
'storage. Run these commands to set up credentials:'),
" phabricator/ $ ./bin/config set mysql.host __host__\n".
" phabricator/ $ ./bin/config set mysql.user __username__\n".
" phabricator/ $ ./bin/config set mysql.pass __password__",
pht(
'These standard credentials are separate from any administrative '.
'credentials provided to this command with __%s__ or '.
'__%s__, and must be configured correctly before you can proceed.',
'--user',
'--password'),
pht('Raw MySQL Error'),
$ex->getMessage());
echo phutil_console_wrap($message);
exit(1);
}
if ($args->getArg('password') === null) {
// This is already a PhutilOpaqueEnvelope.
- $password = $conf->getPassword();
+ $password = $ref->getPass();
} else {
// Put this in a PhutilOpaqueEnvelope.
$password = new PhutilOpaqueEnvelope($args->getArg('password'));
PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password'));
}
+$selected_user = $args->getArg('user');
+if ($selected_user === null) {
+ $selected_user = $default_user;
+}
+
$api = id(new PhabricatorStorageManagementAPI())
- ->setUser($args->getArg('user'))
+ ->setUser($selected_user)
->setHost($default_host)
->setPort($default_port)
->setPassword($password)
->setNamespace($args->getArg('namespace'))
->setDisableUTF8MB4($args->getArg('disable-utf8mb4'));
PhabricatorEnv::overrideConfig('mysql.user', $api->getUser());
try {
queryfx(
$api->getConn(null),
'SELECT 1');
} catch (AphrontQueryException $ex) {
$message = phutil_console_format(
"**%s**\n\n%s\n\n**%s**: %s\n",
pht('Bad Administrative Credentials'),
pht(
'Unable to connect to MySQL using the administrative credentials '.
'provided with the __%s__ and __%s__ flags. Check that '.
'you have entered them correctly.',
'--user',
'--password'),
pht('Raw MySQL Error'),
$ex->getMessage());
echo phutil_console_wrap($message);
exit(1);
}
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorStorageManagementWorkflow')
->execute();
$patches = PhabricatorSQLPatchList::buildAllPatches();
foreach ($workflows as $workflow) {
$workflow->setAPI($api);
$workflow->setPatches($patches);
}
$workflows[] = new PhutilHelpArgumentWorkflow();
$args->parseWorkflows($workflows);
diff --git a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
index 4ee0a75c6f..dab9ad3b54 100644
--- a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
+++ b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
@@ -1,149 +1,134 @@
<?php
final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_IMPORTANT;
}
public function getExecutionOrder() {
// This must run after basic PHP checks, but before most other checks.
return 0.5;
}
protected function executeChecks() {
- $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider');
- $conn_user = $conf->getUser();
- $conn_pass = $conf->getPassword();
- $conn_host = $conf->getHost();
- $conn_port = $conf->getPort();
-
- ini_set('mysql.connect_timeout', 2);
-
- $config = array(
- 'user' => $conn_user,
- 'pass' => $conn_pass,
- 'host' => $conn_host,
- 'port' => $conn_port,
- 'database' => null,
- );
-
- $conn_raw = PhabricatorEnv::newObjectFromConfig(
- 'mysql.implementation',
- array($config));
+ $master = PhabricatorDatabaseRef::getMasterDatabaseRef();
+ if (!$master) {
+ // If we're implicitly in read-only mode during disaster recovery,
+ // don't bother with these setup checks.
+ return;
+ }
+
+ $conn_raw = $master->newManagementConnection();
try {
queryfx($conn_raw, 'SELECT 1');
} catch (AphrontConnectionQueryException $ex) {
$message = pht(
"Unable to connect to MySQL!\n\n".
"%s\n\n".
"Make sure Phabricator and MySQL are correctly configured.",
$ex->getMessage());
$this->newIssue('mysql.connect')
->setName(pht('Can Not Connect to MySQL'))
->setMessage($message)
->setIsFatal(true)
->addRelatedPhabricatorConfig('mysql.host')
->addRelatedPhabricatorConfig('mysql.port')
->addRelatedPhabricatorConfig('mysql.user')
->addRelatedPhabricatorConfig('mysql.pass');
return;
}
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
$engines = ipull($engines, 'Support', 'Engine');
$innodb = idx($engines, 'InnoDB');
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
$message = pht(
"The 'InnoDB' engine is not available in MySQL. Enable InnoDB in ".
"your MySQL configuration.".
"\n\n".
"(If you aleady created tables, MySQL incorrectly used some other ".
"engine to create them. You need to convert them or drop and ".
"reinitialize them.)");
$this->newIssue('mysql.innodb')
->setName(pht('MySQL InnoDB Engine Not Available'))
->setMessage($message)
->setIsFatal(true);
return;
}
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
$databases = ipull($databases, 'Database', 'Database');
if (empty($databases[$namespace.'_meta_data'])) {
$message = pht(
"Run the storage upgrade script to setup Phabricator's database ".
"schema.");
$this->newIssue('storage.upgrade')
->setName(pht('Setup MySQL Schema'))
->setMessage($message)
->setIsFatal(true)
->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
} else {
-
- $config['database'] = $namespace.'_meta_data';
- $conn_meta = PhabricatorEnv::newObjectFromConfig(
- 'mysql.implementation',
- array($config));
+ $conn_meta = $master->newApplicationConnection(
+ $namespace.'_meta_data');
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
$applied = ipull($applied, 'patch', 'patch');
$all = PhabricatorSQLPatchList::buildAllPatches();
$diff = array_diff_key($all, $applied);
if ($diff) {
$this->newIssue('storage.patch')
->setName(pht('Upgrade MySQL Schema'))
->setMessage(
pht(
"Run the storage upgrade script to upgrade Phabricator's ".
"database schema. Missing patches:<br />%s<br />",
phutil_implode_html(phutil_tag('br'), array_keys($diff))))
->addCommand(
hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
}
}
-
$host = PhabricatorEnv::getEnvConfig('mysql.host');
$matches = null;
if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) {
$host = $matches[1];
$port = $matches[2];
$this->newIssue('storage.mysql.hostport')
->setName(pht('Deprecated mysql.host Format'))
->setSummary(
pht(
'Move port information from `%s` to `%s` in your config.',
'mysql.host',
'mysql.port'))
->setMessage(
pht(
'Your `%s` configuration contains a port number, but this usage '.
'is deprecated. Instead, put the port number in `%s`.',
'mysql.host',
'mysql.port'))
->addPhabricatorConfig('mysql.host')
->addPhabricatorConfig('mysql.port')
->addCommand(
hsprintf(
'<tt>phabricator/ $</tt> ./bin/config set mysql.host %s',
$host))
->addCommand(
hsprintf(
'<tt>phabricator/ $</tt> ./bin/config set mysql.port %s',
$port));
}
}
}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
index b8180bda33..ef805f3f28 100644
--- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
@@ -1,321 +1,372 @@
<?php
final class PhabricatorDatabaseRef
extends Phobject {
const STATUS_OKAY = 'okay';
const STATUS_FAIL = 'fail';
const STATUS_AUTH = 'auth';
const STATUS_REPLICATION_CLIENT = 'replication-client';
const REPLICATION_OKAY = 'okay';
const REPLICATION_MASTER_REPLICA = 'master-replica';
const REPLICATION_REPLICA_NONE = 'replica-none';
const REPLICATION_SLOW = 'replica-slow';
private $host;
private $port;
private $user;
private $pass;
private $disabled;
private $isMaster;
private $connectionLatency;
private $connectionStatus;
private $connectionMessage;
private $replicaStatus;
private $replicaMessage;
private $replicaDelay;
public function setHost($host) {
$this->host = $host;
return $this;
}
public function getHost() {
return $this->host;
}
public function setPort($port) {
$this->port = $port;
return $this;
}
public function getPort() {
return $this->port;
}
public function setUser($user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setPass(PhutilOpaqueEnvelope $pass) {
$this->pass = $pass;
return $this;
}
public function getPass() {
return $this->pass;
}
public function setIsMaster($is_master) {
$this->isMaster = $is_master;
return $this;
}
public function getIsMaster() {
return $this->isMaster;
}
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function getDisabled() {
return $this->disabled;
}
public function setConnectionLatency($connection_latency) {
$this->connectionLatency = $connection_latency;
return $this;
}
public function getConnectionLatency() {
return $this->connectionLatency;
}
public function setConnectionStatus($connection_status) {
$this->connectionStatus = $connection_status;
return $this;
}
public function getConnectionStatus() {
if ($this->connectionStatus === null) {
throw new PhutilInvalidStateException('queryAll');
}
return $this->connectionStatus;
}
public function setConnectionMessage($connection_message) {
$this->connectionMessage = $connection_message;
return $this;
}
public function getConnectionMessage() {
return $this->connectionMessage;
}
public function setReplicaStatus($replica_status) {
$this->replicaStatus = $replica_status;
return $this;
}
public function getReplicaStatus() {
return $this->replicaStatus;
}
public function setReplicaMessage($replica_message) {
$this->replicaMessage = $replica_message;
return $this;
}
public function getReplicaMessage() {
return $this->replicaMessage;
}
public function setReplicaDelay($replica_delay) {
$this->replicaDelay = $replica_delay;
return $this;
}
public function getReplicaDelay() {
return $this->replicaDelay;
}
public static function getConnectionStatusMap() {
return array(
self::STATUS_OKAY => array(
'icon' => 'fa-exchange',
'color' => 'green',
'label' => pht('Okay'),
),
self::STATUS_FAIL => array(
'icon' => 'fa-times',
'color' => 'red',
'label' => pht('Failed'),
),
self::STATUS_AUTH => array(
'icon' => 'fa-key',
'color' => 'red',
'label' => pht('Invalid Credentials'),
),
self::STATUS_REPLICATION_CLIENT => array(
'icon' => 'fa-eye-slash',
'color' => 'yellow',
'label' => pht('Missing Permission'),
),
);
}
public static function getReplicaStatusMap() {
return array(
self::REPLICATION_OKAY => array(
'icon' => 'fa-download',
'color' => 'green',
'label' => pht('Okay'),
),
self::REPLICATION_MASTER_REPLICA => array(
'icon' => 'fa-database',
'color' => 'red',
'label' => pht('Replicating Master'),
),
self::REPLICATION_REPLICA_NONE => array(
'icon' => 'fa-download',
'color' => 'red',
'label' => pht('Not Replicating'),
),
self::REPLICATION_SLOW => array(
'icon' => 'fa-hourglass',
'color' => 'red',
'label' => pht('Slow Replication'),
),
);
}
public static function loadAll() {
$refs = array();
$default_port = PhabricatorEnv::getEnvConfig('mysql.port');
$default_port = nonempty($default_port, 3306);
$default_user = PhabricatorEnv::getEnvConfig('mysql.user');
$default_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
$default_pass = new PhutilOpaqueEnvelope($default_pass);
$config = PhabricatorEnv::getEnvConfig('cluster.databases');
foreach ($config as $server) {
$host = $server['host'];
$port = idx($server, 'port', $default_port);
$user = idx($server, 'user', $default_user);
$disabled = idx($server, 'disabled', false);
$pass = idx($server, 'pass');
if ($pass) {
$pass = new PhutilOpaqueEnvelope($pass);
} else {
$pass = clone $default_pass;
}
$role = $server['role'];
$ref = id(new self())
->setHost($host)
->setPort($port)
->setUser($user)
->setPass($pass)
->setDisabled($disabled)
->setIsMaster(($role == 'master'));
$refs[] = $ref;
}
return $refs;
}
public static function queryAll() {
$refs = self::loadAll();
foreach ($refs as $ref) {
if ($ref->getDisabled()) {
continue;
}
- $conn = $ref->newConnection();
+ $conn = $ref->newManagementConnection();
$t_start = microtime(true);
try {
$replica_status = queryfx_one($conn, 'SHOW SLAVE STATUS');
$ref->setConnectionStatus(self::STATUS_OKAY);
} catch (AphrontAccessDeniedQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_REPLICATION_CLIENT);
$ref->setConnectionMessage(
pht(
'No permission to run "SHOW SLAVE STATUS". Grant this user '.
'"REPLICATION CLIENT" permission to allow Phabricator to '.
'monitor replica health.'));
} catch (AphrontInvalidCredentialsQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_AUTH);
$ref->setConnectionMessage($ex->getMessage());
} catch (AphrontQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_FAIL);
$class = get_class($ex);
$message = $ex->getMessage();
$ref->setConnectionMessage(
pht(
'%s: %s',
get_class($ex),
$ex->getMessage()));
}
$t_end = microtime(true);
$ref->setConnectionLatency($t_end - $t_start);
$is_replica = (bool)$replica_status;
if ($ref->getIsMaster() && $is_replica) {
$ref->setReplicaStatus(self::REPLICATION_MASTER_REPLICA);
$ref->setReplicaMessage(
pht(
'This host has a "master" role, but is replicating data from '.
'another host ("%s")!',
idx($replica_status, 'Master_Host')));
} else if (!$ref->getIsMaster() && !$is_replica) {
$ref->setReplicaStatus(self::REPLICATION_REPLICA_NONE);
$ref->setReplicaMessage(
pht(
'This host has a "replica" role, but is not replicating data '.
'from a master (no output from "SHOW SLAVE STATUS").'));
} else {
$ref->setReplicaStatus(self::REPLICATION_OKAY);
}
if ($is_replica) {
$latency = (int)idx($replica_status, 'Seconds_Behind_Master');
$ref->setReplicaDelay($latency);
if ($latency > 30) {
$ref->setReplicaStatus(self::REPLICATION_SLOW);
$ref->setReplicaMessage(
pht(
'This replica is lagging far behind the master. Data is at '.
'risk!'));
}
}
}
return $refs;
}
- protected function newConnection() {
+ public function newManagementConnection() {
+ return $this->newConnection(
+ array(
+ 'retries' => 0,
+ 'timeout' => 3,
+ ));
+ }
+
+ public function newApplicationConnection($database) {
+ return $this->newConnection(
+ array(
+ 'database' => $database,
+ ));
+ }
+
+ public static function getMasterDatabaseRef() {
+ $refs = self::loadAll();
+
+ if (!$refs) {
+ $conf = PhabricatorEnv::newObjectFromConfig(
+ 'mysql.configuration-provider',
+ array(null, 'w', null));
+
+ return id(new self())
+ ->setHost($conf->getHost())
+ ->setPort($conf->getPort())
+ ->setUser($conf->getUser())
+ ->setPass($conf->getPassword())
+ ->setIsMaster(true);
+ }
+
+ $master = null;
+ foreach ($refs as $ref) {
+ if ($ref->getDisabled()) {
+ continue;
+ }
+ if ($ref->getIsMaster()) {
+ return $ref;
+ }
+ }
+
+ return null;
+ }
+
+ private function newConnection(array $options) {
+ $spec = $options + array(
+ 'user' => $this->getUser(),
+ 'pass' => $this->getPass(),
+ 'host' => $this->getHost(),
+ 'port' => $this->getPort(),
+ 'database' => null,
+ 'retries' => 3,
+ 'timeout' => 15,
+ );
+
+ // TODO: Remove this once the MySQL connector has proper support
+ // for it, see T6710.
+ ini_set('mysql.connect_timeout', $spec['timeout']);
+
return PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array(
- array(
- 'user' => $this->getUser(),
- 'pass' => $this->getPass(),
- 'host' => $this->getHost(),
- 'port' => $this->getPort(),
- 'database' => null,
- 'retries' => 0,
- ),
+ $spec,
));
}
}
diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
index afd2a922e9..9641502ba3 100644
--- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
+++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
@@ -1,263 +1,286 @@
<?php
/**
* @task config Configuring Storage
*/
abstract class PhabricatorLiskDAO extends LiskDAO {
private static $namespaceStack = array();
const ATTACHABLE = '<attachable>';
const CONFIG_APPLICATION_SERIALIZERS = 'phabricator/serializers';
/* -( 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(pht('No storage namespace configured!'));
}
return $namespace;
}
/**
* @task config
*/
protected function establishLiveConnection($mode) {
$namespace = self::getStorageNamespace();
-
- $conf = PhabricatorEnv::newObjectFromConfig(
- 'mysql.configuration-provider',
- array($this, $mode, $namespace));
+ $database = $namespace.'_'.$this->getApplicationName();
$is_readonly = PhabricatorEnv::isReadOnly();
+
if ($is_readonly && ($mode != 'r')) {
throw new Exception(
pht(
'Attempting to establish write-mode connection from a read-only '.
'page (to database "%s").',
- $conf->getDatabase()));
+ $database));
}
- $connection = PhabricatorEnv::newObjectFromConfig(
+ $refs = PhabricatorDatabaseRef::loadAll();
+ if ($refs) {
+ $connection = $this->newClusterConnection($database);
+ } else {
+ $connection = $this->newBasicConnection($database, $mode, $namespace);
+ }
+
+ // TODO: This should be testing if the mode is "r", but that would proably
+ // break a lot of things. Perform a more narrow test for readonly mode
+ // until we have greater certainty that this works correctly most of the
+ // time.
+ if ($is_readonly) {
+ $connection->setReadOnly(true);
+ }
+
+ return $connection;
+ }
+
+ private function newBasicConnection($database, $mode, $namespace) {
+ $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(),
+ 'database' => $database,
'retries' => 3,
),
));
+ }
- // TODO: This should be testing if the mode is "r", but that would proably
- // break a lot of things. Perform a more narrow test for readonly mode
- // until we have greater certainty that this works correctly most of the
- // time.
- if ($is_readonly) {
- $connection->setReadOnly(true);
+ private function newClusterConnection($database) {
+ $master = PhabricatorDatabaseRef::getMasterDatabaseRef();
+
+ if (!$master) {
+ // TODO: Implicitly degrade to read-only mode.
+ throw new Exception(pht('No master in database cluster config!'));
}
- return $connection;
+ return $master->newApplicationConnection($database);
}
+
/**
* @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);
}
protected function willReadData(array &$data) {
parent::willReadData($data);
static $custom;
if ($custom === null) {
$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);
}
if ($custom) {
foreach ($custom as $key => $serializer) {
$data[$key] = $serializer->willReadValue($data[$key]);
}
}
}
protected function willWriteData(array &$data) {
static $custom;
if ($custom === null) {
$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);
}
if ($custom) {
foreach ($custom as $key => $serializer) {
$data[$key] = $serializer->willWriteValue($data[$key]);
}
}
parent::willWriteData($data);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Mar 16, 9:34 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
962544
Default Alt Text
(30 KB)

Event Timeline