1
0
Fork 0
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:
vrana 2012-04-05 17:45:11 -07:00
parent 2211a0b07e
commit e69ba98e20
10 changed files with 238 additions and 67 deletions

View file

@ -62,8 +62,10 @@ phutil_register_library_map(array(
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
'AphrontListFilterView' => 'view/layout/listfilter',
'AphrontMiniPanelView' => 'view/layout/minipanel',
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql/mysql',
'AphrontMySQLDatabaseConnectionBase' => 'storage/connection/mysql/base',
'AphrontMySQLDatabaseConnectionTestCase' => 'storage/connection/mysql/__tests__',
'AphrontMySQLiDatabaseConnection' => 'storage/connection/mysql/mysqli',
'AphrontNullView' => 'view/null',
'AphrontPHPHTTPSink' => 'aphront/sink/php',
'AphrontPageView' => 'view/page/base',

View file

@ -7,7 +7,7 @@
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', 'view/control/table');

View file

@ -7,7 +7,7 @@
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('phutil', 'symbols');

View file

@ -9,7 +9,7 @@
phutil_require_module('phabricator', 'applications/base/storage/configuration');
phutil_require_module('phabricator', 'infrastructure/env');
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('phutil', 'filesystem');

View file

@ -19,7 +19,8 @@
/**
* @group storage
*/
final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
abstract class AphrontMySQLDatabaseConnectionBase
extends AphrontDatabaseConnection {
private $configuration;
private $connection;
@ -28,15 +29,16 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
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) {
$this->configuration = $configuration;
}
public function escapeString($string) {
$this->requireConnection();
return mysql_real_escape_string($string, $this->connection);
}
public function escapeColumnName($name) {
return '`'.str_replace('`', '``', $name).'`';
}
@ -85,7 +87,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
return $value;
}
private function getConfiguration($key, $default = null) {
protected function getConfiguration($key, $default = null) {
return idx($this->configuration, $key, $default);
}
@ -105,7 +107,6 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
return "{$user}:{$host}:{$database}";
}
private function establishConnection() {
$this->closeConnection();
@ -117,17 +118,6 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
$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');
$database = $this->getConfiguration('database');
@ -143,32 +133,10 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
$retries = max(1, PhabricatorEnv::getEnvConfig('mysql.connection-retries'));
while ($retries--) {
try {
$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);
}
$conn = $this->connect();
$profiler->endServiceCall($call_id, array());
break;
} catch (Exception $ex) {
} catch (AphrontQueryException $ex) {
if ($retries && $ex->getCode() == 2003) {
$class = get_class($ex);
$message = $ex->getMessage();
@ -184,19 +152,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
$this->connection = $conn;
}
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();
}
private function requireConnection() {
protected function requireConnection() {
if (!$this->connection) {
$this->establishConnection();
}
@ -209,7 +165,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
if ($res == null) {
throw new Exception('No query result to fetch from!');
}
while (($row = mysql_fetch_assoc($res)) !== false) {
while (($row = $this->fetchAssoc($res))) {
$result[] = $row;
}
return $result;
@ -239,7 +195,7 @@ final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
'write' => $is_write,
));
$result = @mysql_query($raw_query, $this->connection);
$result = $this->rawQuery($raw_query);
$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) {
$errno = $this->nextError;
$error = 'Simulated error.';
$this->nextError = null;
} else {
$errno = mysql_errno($connection);
$error = mysql_error($connection);
$errno = $this->getErrorCode($connection);
$error = $this->getErrorDescription($connection);
}
$exmsg = "#{$errno}: {$error}";

View file

@ -11,7 +11,6 @@ phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'storage/connection/base');
phutil_require_module('phabricator', 'storage/exception/accessdenied');
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/duplicatekey');
phutil_require_module('phabricator', 'storage/exception/recoverable');
@ -22,4 +21,4 @@ phutil_require_module('phutil', 'serviceprofiler');
phutil_require_module('phutil', 'utils');
phutil_require_source('AphrontMySQLDatabaseConnection.php');
phutil_require_source('AphrontMySQLDatabaseConnectionBase.php');

View file

@ -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);
}
}

View 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');

View file

@ -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;
}
}

View 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');