mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 05:50:55 +01:00
Prepare for MySQLi support
Summary: This separates common MySQL stuff (identifiers and comments escaping, error codes, connection retries) from PHP extension specific stuff (connect, query, fetch, errors, escape string). Test Plan: / Use `AphrontMySQLiDatabaseConnection` in `PhabricatorLiskDAO`, load homepage, edit task, save task. Reviewers: epriestley Reviewed By: epriestley CC: nh, aran Differential Revision: https://secure.phabricator.com/D2113
This commit is contained in:
parent
2211a0b07e
commit
e69ba98e20
10 changed files with 238 additions and 67 deletions
|
@ -62,8 +62,10 @@ phutil_register_library_map(array(
|
||||||
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
|
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
|
||||||
'AphrontListFilterView' => 'view/layout/listfilter',
|
'AphrontListFilterView' => 'view/layout/listfilter',
|
||||||
'AphrontMiniPanelView' => 'view/layout/minipanel',
|
'AphrontMiniPanelView' => 'view/layout/minipanel',
|
||||||
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
|
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql/mysql',
|
||||||
|
'AphrontMySQLDatabaseConnectionBase' => 'storage/connection/mysql/base',
|
||||||
'AphrontMySQLDatabaseConnectionTestCase' => 'storage/connection/mysql/__tests__',
|
'AphrontMySQLDatabaseConnectionTestCase' => 'storage/connection/mysql/__tests__',
|
||||||
|
'AphrontMySQLiDatabaseConnection' => 'storage/connection/mysql/mysqli',
|
||||||
'AphrontNullView' => 'view/null',
|
'AphrontNullView' => 'view/null',
|
||||||
'AphrontPHPHTTPSink' => 'aphront/sink/php',
|
'AphrontPHPHTTPSink' => 'aphront/sink/php',
|
||||||
'AphrontPageView' => 'view/page/base',
|
'AphrontPageView' => 'view/page/base',
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'aphront/console/plugin/base');
|
phutil_require_module('phabricator', 'aphront/console/plugin/base');
|
||||||
phutil_require_module('phabricator', 'storage/connection/mysql');
|
phutil_require_module('phabricator', 'storage/connection/mysql/mysql');
|
||||||
phutil_require_module('phabricator', 'storage/queryfx');
|
phutil_require_module('phabricator', 'storage/queryfx');
|
||||||
phutil_require_module('phabricator', 'view/control/table');
|
phutil_require_module('phabricator', 'view/control/table');
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'storage/connection/mysql');
|
phutil_require_module('phabricator', 'storage/connection/mysql/mysql');
|
||||||
phutil_require_module('phabricator', 'storage/lisk/dao');
|
phutil_require_module('phabricator', 'storage/lisk/dao');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'symbols');
|
phutil_require_module('phutil', 'symbols');
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'applications/base/storage/configuration');
|
phutil_require_module('phabricator', 'applications/base/storage/configuration');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'infrastructure/setup/sql');
|
phutil_require_module('phabricator', 'infrastructure/setup/sql');
|
||||||
phutil_require_module('phabricator', 'storage/connection/mysql');
|
phutil_require_module('phabricator', 'storage/connection/mysql/mysql');
|
||||||
phutil_require_module('phabricator', 'storage/queryfx');
|
phutil_require_module('phabricator', 'storage/queryfx');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'filesystem');
|
phutil_require_module('phutil', 'filesystem');
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
/**
|
/**
|
||||||
* @group storage
|
* @group storage
|
||||||
*/
|
*/
|
||||||
final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
abstract class AphrontMySQLDatabaseConnectionBase
|
||||||
|
extends AphrontDatabaseConnection {
|
||||||
|
|
||||||
private $configuration;
|
private $configuration;
|
||||||
private $connection;
|
private $connection;
|
||||||
|
@ -28,15 +29,16 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
|
|
||||||
private static $connectionCache = array();
|
private static $connectionCache = array();
|
||||||
|
|
||||||
|
abstract protected function connect();
|
||||||
|
abstract protected function rawQuery($raw_query);
|
||||||
|
abstract protected function fetchAssoc($result);
|
||||||
|
abstract protected function getErrorCode($connection);
|
||||||
|
abstract protected function getErrorDescription($connection);
|
||||||
|
|
||||||
public function __construct(array $configuration) {
|
public function __construct(array $configuration) {
|
||||||
$this->configuration = $configuration;
|
$this->configuration = $configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function escapeString($string) {
|
|
||||||
$this->requireConnection();
|
|
||||||
return mysql_real_escape_string($string, $this->connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function escapeColumnName($name) {
|
public function escapeColumnName($name) {
|
||||||
return '`'.str_replace('`', '``', $name).'`';
|
return '`'.str_replace('`', '``', $name).'`';
|
||||||
}
|
}
|
||||||
|
@ -85,7 +87,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getConfiguration($key, $default = null) {
|
protected function getConfiguration($key, $default = null) {
|
||||||
return idx($this->configuration, $key, $default);
|
return idx($this->configuration, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +107,6 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
return "{$user}:{$host}:{$database}";
|
return "{$user}:{$host}:{$database}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function establishConnection() {
|
private function establishConnection() {
|
||||||
$this->closeConnection();
|
$this->closeConnection();
|
||||||
|
|
||||||
|
@ -117,17 +118,6 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
|
|
||||||
$start = microtime(true);
|
$start = microtime(true);
|
||||||
|
|
||||||
if (!function_exists('mysql_connect')) {
|
|
||||||
// We have to '@' the actual call since it can spew all sorts of silly
|
|
||||||
// noise, but it will also silence fatals caused by not having MySQL
|
|
||||||
// installed, which has bitten me on three separate occasions. Make sure
|
|
||||||
// such failures are explicit and loud.
|
|
||||||
throw new Exception(
|
|
||||||
"About to call mysql_connect(), but the PHP MySQL extension is not ".
|
|
||||||
"available!");
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->getConfiguration('user');
|
|
||||||
$host = $this->getConfiguration('host');
|
$host = $this->getConfiguration('host');
|
||||||
$database = $this->getConfiguration('database');
|
$database = $this->getConfiguration('database');
|
||||||
|
|
||||||
|
@ -143,32 +133,10 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
$retries = max(1, PhabricatorEnv::getEnvConfig('mysql.connection-retries'));
|
$retries = max(1, PhabricatorEnv::getEnvConfig('mysql.connection-retries'));
|
||||||
while ($retries--) {
|
while ($retries--) {
|
||||||
try {
|
try {
|
||||||
$conn = @mysql_connect(
|
$conn = $this->connect();
|
||||||
$host,
|
|
||||||
$user,
|
|
||||||
$this->getConfiguration('pass'),
|
|
||||||
$new_link = true,
|
|
||||||
$flags = 0);
|
|
||||||
|
|
||||||
if (!$conn) {
|
|
||||||
$errno = mysql_errno();
|
|
||||||
$error = mysql_error();
|
|
||||||
throw new AphrontQueryConnectionException(
|
|
||||||
"Attempt to connect to {$user}@{$host} failed with error ".
|
|
||||||
"#{$errno}: {$error}.", $errno);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($database !== null) {
|
|
||||||
$ret = @mysql_select_db($database, $conn);
|
|
||||||
if (!$ret) {
|
|
||||||
$this->throwQueryException($conn);
|
|
||||||
}
|
|
||||||
mysql_set_charset('utf8', $conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
$profiler->endServiceCall($call_id, array());
|
$profiler->endServiceCall($call_id, array());
|
||||||
break;
|
break;
|
||||||
} catch (Exception $ex) {
|
} catch (AphrontQueryException $ex) {
|
||||||
if ($retries && $ex->getCode() == 2003) {
|
if ($retries && $ex->getCode() == 2003) {
|
||||||
$class = get_class($ex);
|
$class = get_class($ex);
|
||||||
$message = $ex->getMessage();
|
$message = $ex->getMessage();
|
||||||
|
@ -184,19 +152,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
$this->connection = $conn;
|
$this->connection = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInsertID() {
|
protected function requireConnection() {
|
||||||
return mysql_insert_id($this->requireConnection());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAffectedRows() {
|
|
||||||
return mysql_affected_rows($this->requireConnection());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getTransactionKey() {
|
|
||||||
return (int)$this->requireConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function requireConnection() {
|
|
||||||
if (!$this->connection) {
|
if (!$this->connection) {
|
||||||
$this->establishConnection();
|
$this->establishConnection();
|
||||||
}
|
}
|
||||||
|
@ -209,7 +165,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
if ($res == null) {
|
if ($res == null) {
|
||||||
throw new Exception('No query result to fetch from!');
|
throw new Exception('No query result to fetch from!');
|
||||||
}
|
}
|
||||||
while (($row = mysql_fetch_assoc($res)) !== false) {
|
while (($row = $this->fetchAssoc($res))) {
|
||||||
$result[] = $row;
|
$result[] = $row;
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
|
@ -239,7 +195,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
'write' => $is_write,
|
'write' => $is_write,
|
||||||
));
|
));
|
||||||
|
|
||||||
$result = @mysql_query($raw_query, $this->connection);
|
$result = $this->rawQuery($raw_query);
|
||||||
|
|
||||||
$profiler->endServiceCall($call_id, array());
|
$profiler->endServiceCall($call_id, array());
|
||||||
|
|
||||||
|
@ -284,14 +240,14 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function throwQueryException($connection) {
|
protected function throwQueryException($connection) {
|
||||||
if ($this->nextError) {
|
if ($this->nextError) {
|
||||||
$errno = $this->nextError;
|
$errno = $this->nextError;
|
||||||
$error = 'Simulated error.';
|
$error = 'Simulated error.';
|
||||||
$this->nextError = null;
|
$this->nextError = null;
|
||||||
} else {
|
} else {
|
||||||
$errno = mysql_errno($connection);
|
$errno = $this->getErrorCode($connection);
|
||||||
$error = mysql_error($connection);
|
$error = $this->getErrorDescription($connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
$exmsg = "#{$errno}: {$error}";
|
$exmsg = "#{$errno}: {$error}";
|
|
@ -11,7 +11,6 @@ phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'storage/connection/base');
|
phutil_require_module('phabricator', 'storage/connection/base');
|
||||||
phutil_require_module('phabricator', 'storage/exception/accessdenied');
|
phutil_require_module('phabricator', 'storage/exception/accessdenied');
|
||||||
phutil_require_module('phabricator', 'storage/exception/base');
|
phutil_require_module('phabricator', 'storage/exception/base');
|
||||||
phutil_require_module('phabricator', 'storage/exception/connection');
|
|
||||||
phutil_require_module('phabricator', 'storage/exception/connectionlost');
|
phutil_require_module('phabricator', 'storage/exception/connectionlost');
|
||||||
phutil_require_module('phabricator', 'storage/exception/duplicatekey');
|
phutil_require_module('phabricator', 'storage/exception/duplicatekey');
|
||||||
phutil_require_module('phabricator', 'storage/exception/recoverable');
|
phutil_require_module('phabricator', 'storage/exception/recoverable');
|
||||||
|
@ -22,4 +21,4 @@ phutil_require_module('phutil', 'serviceprofiler');
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('AphrontMySQLDatabaseConnection.php');
|
phutil_require_source('AphrontMySQLDatabaseConnectionBase.php');
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group storage
|
||||||
|
*/
|
||||||
|
final class AphrontMySQLDatabaseConnection
|
||||||
|
extends AphrontMySQLDatabaseConnectionBase {
|
||||||
|
|
||||||
|
public function escapeString($string) {
|
||||||
|
return mysql_real_escape_string($string, $this->requireConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInsertID() {
|
||||||
|
return mysql_insert_id($this->requireConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectedRows() {
|
||||||
|
return mysql_affected_rows($this->requireConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTransactionKey() {
|
||||||
|
return (int)$this->requireConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function connect() {
|
||||||
|
if (!function_exists('mysql_connect')) {
|
||||||
|
// We have to '@' the actual call since it can spew all sorts of silly
|
||||||
|
// noise, but it will also silence fatals caused by not having MySQL
|
||||||
|
// installed, which has bitten me on three separate occasions. Make sure
|
||||||
|
// such failures are explicit and loud.
|
||||||
|
throw new Exception(
|
||||||
|
"About to call mysql_connect(), but the PHP MySQL extension is not ".
|
||||||
|
"available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->getConfiguration('user');
|
||||||
|
$host = $this->getConfiguration('host');
|
||||||
|
$database = $this->getConfiguration('database');
|
||||||
|
|
||||||
|
$conn = @mysql_connect(
|
||||||
|
$host,
|
||||||
|
$user,
|
||||||
|
$this->getConfiguration('pass'),
|
||||||
|
$new_link = true,
|
||||||
|
$flags = 0);
|
||||||
|
|
||||||
|
if (!$conn) {
|
||||||
|
$errno = mysql_errno();
|
||||||
|
$error = mysql_error();
|
||||||
|
throw new AphrontQueryConnectionException(
|
||||||
|
"Attempt to connect to {$user}@{$host} failed with error ".
|
||||||
|
"#{$errno}: {$error}.", $errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($database !== null) {
|
||||||
|
$ret = @mysql_select_db($database, $conn);
|
||||||
|
if (!$ret) {
|
||||||
|
$this->throwQueryException($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_set_charset('utf8', $conn);
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rawQuery($raw_query) {
|
||||||
|
return @mysql_query($raw_query, $this->requireConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fetchAssoc($result) {
|
||||||
|
return mysql_fetch_assoc($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getErrorCode($connection) {
|
||||||
|
return mysql_errno($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getErrorDescription($connection) {
|
||||||
|
return mysql_error($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/storage/connection/mysql/mysql/__init__.php
Normal file
13
src/storage/connection/mysql/mysql/__init__.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'storage/connection/mysql/base');
|
||||||
|
phutil_require_module('phabricator', 'storage/exception/connection');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('AphrontMySQLDatabaseConnection.php');
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group storage
|
||||||
|
*
|
||||||
|
* @phutil-external-symbol class mysqli
|
||||||
|
*/
|
||||||
|
final class AphrontMySQLiDatabaseConnection
|
||||||
|
extends AphrontMySQLDatabaseConnectionBase {
|
||||||
|
|
||||||
|
public function escapeString($string) {
|
||||||
|
return $this->requireConnection()->escape_string($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInsertID() {
|
||||||
|
return $this->requireConnection()->insert_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectedRows() {
|
||||||
|
return $this->requireConnection()->affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTransactionKey() {
|
||||||
|
return spl_object_hash($this->requireConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function connect() {
|
||||||
|
if (!class_exists('mysqli', false)) {
|
||||||
|
throw new Exception(
|
||||||
|
"About to call new mysqli(), but the PHP MySQLi extension is not ".
|
||||||
|
"available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->getConfiguration('user');
|
||||||
|
$host = $this->getConfiguration('host');
|
||||||
|
$database = $this->getConfiguration('database');
|
||||||
|
|
||||||
|
$conn = @new mysqli(
|
||||||
|
$host,
|
||||||
|
$user,
|
||||||
|
$this->getConfiguration('pass'),
|
||||||
|
$database);
|
||||||
|
|
||||||
|
$errno = $conn->connect_errno;
|
||||||
|
if ($errno) {
|
||||||
|
$error = $conn->connect_error;
|
||||||
|
throw new AphrontQueryConnectionException(
|
||||||
|
"Attempt to connect to {$user}@{$host} failed with error ".
|
||||||
|
"#{$errno}: {$error}.", $errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->set_charset('utf8');
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rawQuery($raw_query) {
|
||||||
|
return @$this->requireConnection()->query($raw_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fetchAssoc($result) {
|
||||||
|
return $result->fetch_assoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getErrorCode($connection) {
|
||||||
|
return $connection->errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getErrorDescription($connection) {
|
||||||
|
return $connection->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/storage/connection/mysql/mysqli/__init__.php
Normal file
13
src/storage/connection/mysql/mysqli/__init__.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'storage/connection/mysql/base');
|
||||||
|
phutil_require_module('phabricator', 'storage/exception/connection');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('AphrontMySQLiDatabaseConnection.php');
|
Loading…
Reference in a new issue