Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F800176
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, Feb 22, 12:03 AM
Size
67 KB
Mime Type
text/x-diff
Expires
Mon, Feb 24, 12:03 AM (10 h, 55 m)
Engine
blob
Format
Raw Data
Handle
573438
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.3.x/core/kernel/db/db_load_balancer.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_load_balancer.php (revision 15921)
+++ branches/5.3.x/core/kernel/db/db_load_balancer.php (revision 15922)
@@ -1,845 +1,870 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kDBLoadBalancer extends kBase implements IDBConnection {
/**
* Current database type
*
* @var string
* @access protected
*/
protected $dbType = 'mysql';
/**
* Function to handle sql errors
*
* @var string
* @access public
*/
public $errorHandler = '';
/**
* Database connection settings
*
* @var Array
* @access protected
*/
protected $servers = Array ();
/**
* Load of each slave server, given
*
* @var Array
* @access protected
*/
protected $serverLoads = Array ();
/**
* Caches replication lag of servers
*
* @var Array
* @access protected
*/
protected $serverLagTimes = Array ();
/**
* Connection references to opened connections
*
* @var Array
* @access protected
*/
protected $connections = Array ();
/**
* Index of last user slave connection
*
* @var int
* @access protected
*/
protected $slaveIndex = false;
/**
* Index of the server, that was used last
*
* @var int
* @access protected
*/
protected $lastUsedIndex = 0;
/**
* Consider slave down if it isn't responding for N milliseconds
*
* @var int
* @access protected
*/
protected $DBClusterTimeout = 10;
/**
* Scale load balancer polling time so that under overload conditions, the database server
* receives a SHOW STATUS query at an average interval of this many microseconds
*
* @var int
* @access protected
*/
protected $DBAvgStatusPoll = 2000;
/**
* Indicates, that next database query could be cached, when memory caching is enabled
*
* @var bool
* @access public
*/
public $nextQueryCachable = false;
/**
* Indicates, that next query should be sent to maser database
*
* @var bool
*/
public $nextQueryFromMaster = false;
/**
* Creates new instance of load balancer class
*
* @param string $dbType
* @param Array|string $errorHandler
*/
function __construct($dbType, $errorHandler = '')
{
parent::__construct();
$this->dbType = $dbType;
$this->errorHandler = $errorHandler;
$this->DBClusterTimeout *= 1e6; // convert to milliseconds
}
/**
- * Setups load balancer according given configuration
+ * Setups load balancer according given configuration.
*
* @param Array $config
* @return void
* @access public
*/
public function setup($config)
{
$this->servers = Array ();
$this->servers[0] = Array (
'DBHost' => $config['Database']['DBHost'],
'DBUser' => $config['Database']['DBUser'],
'DBUserPassword' => $config['Database']['DBUserPassword'],
'DBName' => $config['Database']['DBName'],
'DBLoad' => 0,
);
if ( isset($config['Databases']) ) {
$this->servers = array_merge($this->servers, $config['Databases']);
}
foreach ($this->servers as $server_index => $server_setting) {
$this->serverLoads[$server_index] = $server_setting['DBLoad'];
}
}
/**
* Returns connection index to master database
*
* @return int
* @access protected
*/
protected function getMasterIndex()
{
return 0;
}
/**
* Returns connection index to slave database. This takes into account load ratios and lag times.
* Side effect: opens connections to databases
*
* @return int
* @access protected
*/
protected function getSlaveIndex()
{
if ( count($this->servers) == 1 || $this->Application->isAdmin ) {
// skip the load balancing if there's only one server OR in admin console
return 0;
}
elseif ( $this->slaveIndex !== false ) {
// shortcut if generic reader exists already
return $this->slaveIndex;
}
$total_elapsed = 0;
$non_error_loads = $this->serverLoads;
$i = $found = $lagged_slave_mode = false;
// first try quickly looking through the available servers for a server that meets our criteria
do {
$current_loads = $non_error_loads;
$overloaded_servers = $total_threads_connected = 0;
while ($current_loads) {
if ( $lagged_slave_mode ) {
// when all slave servers are too lagged, then ignore lag and pick random server
$i = $this->pickRandom($current_loads);
}
else {
$i = $this->getRandomNonLagged($current_loads);
if ( $i === false && $current_loads ) {
// all slaves lagged -> pick random lagged slave then
$lagged_slave_mode = true;
$i = $this->pickRandom( $current_loads );
}
}
if ( $i === false ) {
// all slaves are down -> use master as a slave
$this->slaveIndex = $this->getMasterIndex();
return $this->slaveIndex;
}
$conn =& $this->openConnection($i);
if ( !$conn ) {
unset($non_error_loads[$i], $current_loads[$i]);
continue;
}
// Perform post-connection backoff
$threshold = isset($this->servers[$i]['DBMaxThreads']) ? $this->servers[$i]['DBMaxThreads'] : false;
$backoff = $this->postConnectionBackoff($conn, $threshold);
if ( $backoff ) {
// post-connection overload, don't use this server for now
$total_threads_connected += $backoff;
$overloaded_servers++;
unset( $current_loads[$i] );
}
else {
// return this server
break 2;
}
}
// no server found yet
$i = false;
// if all servers were down, quit now
if ( !$non_error_loads ) {
break;
}
// back off for a while
// scale the sleep time by the number of connected threads, to produce a roughly constant global poll rate
$avg_threads = $total_threads_connected / $overloaded_servers;
usleep($this->DBAvgStatusPoll * $avg_threads);
$total_elapsed += $this->DBAvgStatusPoll * $avg_threads;
} while ( $total_elapsed < $this->DBClusterTimeout );
if ( $i !== false ) {
// slave connection successful
if ( $this->slaveIndex <= 0 && $this->serverLoads[$i] > 0 && $i !== false ) {
$this->slaveIndex = $i;
}
}
return $i;
}
/**
* Returns random non-lagged server
*
* @param Array $loads
* @return int
* @access protected
*/
protected function getRandomNonLagged($loads)
{
// unset excessively lagged servers
$lags = $this->getLagTimes();
foreach ($lags as $i => $lag) {
if ( $i != 0 && isset($this->servers[$i]['DBMaxLag']) ) {
if ( $lag === false ) {
unset( $loads[$i] ); // server is not replicating
}
elseif ( $lag > $this->servers[$i]['DBMaxLag'] ) {
unset( $loads[$i] ); // server is excessively lagged
}
}
}
// find out if all the slaves with non-zero load are lagged
if ( !$loads || array_sum($loads) == 0 ) {
return false;
}
// return a random representative of the remainder
return $this->pickRandom($loads);
}
/**
* Select an element from an array of non-normalised probabilities
*
* @param Array $weights
* @return int
* @access protected
*/
protected function pickRandom($weights)
{
if ( !is_array($weights) || !$weights ) {
return false;
}
$sum = array_sum($weights);
if ( $sum == 0 ) {
return false;
}
$max = mt_getrandmax();
$rand = mt_rand(0, $max) / $max * $sum;
$index = $sum = 0;
foreach ($weights as $index => $weight) {
$sum += $weight;
if ( $sum >= $rand ) {
break;
}
}
return $index;
}
/**
* Get lag time for each server
* Results are cached for a short time in memcached, and indefinitely in the process cache
*
* @return Array
* @access protected
*/
protected function getLagTimes()
{
if ( $this->serverLagTimes ) {
return $this->serverLagTimes;
}
$expiry = 5;
$request_rate = 10;
$cache_key = 'lag_times:' . $this->servers[0]['DBHost'];
$times = $this->Application->getCache($cache_key);
if ( $times ) {
// randomly recache with probability rising over $expiry
$elapsed = adodb_mktime() - $times['timestamp'];
$chance = max(0, ($expiry - $elapsed) * $request_rate);
if ( mt_rand(0, $chance) != 0 ) {
unset( $times['timestamp'] );
$this->serverLagTimes = $times;
return $times;
}
}
// cache key missing or expired
$times = Array();
foreach ($this->servers as $index => $server) {
if ($index == 0) {
$times[$index] = 0; // master
}
else {
$conn =& $this->openConnection($index);
if ($conn !== false) {
$times[$index] = $conn->getSlaveLag();
}
}
}
// add a timestamp key so we know when it was cached
$times['timestamp'] = adodb_mktime();
$this->Application->setCache($cache_key, $times, $expiry);
// but don't give the timestamp to the caller
unset($times['timestamp']);
$this->serverLagTimes = $times;
return $this->serverLagTimes;
}
/**
* Determines whatever server should not be used, even, when connection was made
*
* @param kDBConnection $conn
* @param int $threshold
* @return int
* @access protected
*/
protected function postConnectionBackoff(&$conn, $threshold)
{
if ( !$threshold ) {
return 0;
}
$status = $conn->getStatus('Thread%');
return $status['Threads_running'] > $threshold ? $status['Threads_connected'] : 0;
}
/**
* Open a connection to the server given by the specified index
* Index must be an actual index into the array.
* If the server is already open, returns it.
*
* On error, returns false.
*
* @param integer $i Server index
* @return kDBConnection|false
* @access protected
*/
protected function &openConnection($i)
{
if ( isset($this->connections[$i]) ) {
$conn =& $this->connections[$i];
}
else {
$server = $this->servers[$i];
$server['serverIndex'] = $i;
$conn =& $this->reallyOpenConnection($server, $i == $this->getMasterIndex());
if ( $conn->connectionOpened ) {
$this->connections[$i] =& $conn;
$this->lastUsedIndex = $i;
}
else {
$conn = false;
}
}
if ( $this->nextQueryCachable && is_object($conn) ) {
$conn->nextQueryCachable = true;
$this->nextQueryCachable = false;
}
return $conn;
}
/**
- * Checks if connection to the server given by the specified index is opened
+ * Checks if previous query execution raised an error.
+ *
+ * @return bool
+ * @access public
+ */
+ public function hasError()
+ {
+ $conn =& $this->openConnection($this->lastUsedIndex);
+
+ return $conn->hasError();
+ }
+
+ /**
+ * Checks if connection to the server given by the specified index is opened.
*
* @param integer $i Server index
* @return bool
* @access public
*/
public function connectionOpened($i = null)
{
$conn =& $this->openConnection(isset($i) ? $i : $this->getMasterIndex());
return $conn ? $conn->connectionOpened : false;
}
/**
* Really opens a connection.
* Returns a database object whether or not the connection was successful.
*
* @param Array $server
* @param bool $is_master
* @return kDBConnection
*/
protected function &reallyOpenConnection($server, $is_master)
{
$debug_mode = $this->Application->isDebugMode();
$db_class = $debug_mode ? 'kDBConnectionDebug' : 'kDBConnection';
$db = $this->Application->makeClass($db_class, Array ($this->dbType, $this->errorHandler, $server['serverIndex']));
/* @var $db kDBConnection */
$db->debugMode = $debug_mode;
$db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], true, !$is_master);
return $db;
}
/**
- * Returns first field of first line of recordset if query ok or false otherwise
+ * Returns first field of first line of recordset if query ok or false otherwise.
*
* @param string $sql
* @param int $offset
* @return string
* @access public
*/
public function GetOne($sql, $offset = 0)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetOne($sql, $offset);
}
/**
- * Returns first row of recordset if query ok, false otherwise
+ * Returns first row of recordset if query ok, false otherwise.
*
* @param string $sql
* @param int $offset
* @return Array
* @access public
*/
public function GetRow($sql, $offset = 0)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetRow($sql, $offset);
}
/**
- * Returns 1st column of recordset as one-dimensional array or false otherwise
- * Optional parameter $key_field can be used to set field name to be used as resulting array key
+ * Returns 1st column of recordset as one-dimensional array or false otherwise.
+ *
+ * Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return Array
* @access public
*/
public function GetCol($sql, $key_field = null)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetCol($sql, $key_field);
}
/**
* Returns iterator for 1st column of a recordset or false in case of error.
- * Optional parameter $key_field can be used to set field name to be used
- * as resulting array key.
+ *
+ * Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return bool|kMySQLQueryCol
*/
public function GetColIterator($sql, $key_field = null)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetColIterator($sql, $key_field);
}
/**
- * Queries db with $sql query supplied and returns rows selected if any, false
- * otherwise. Optional parameter $key_field allows to set one of the query fields
- * value as key in string array.
+ * Queries db with $sql query supplied and returns rows selected if any, false otherwise.
+ *
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return Array
* @access public
*/
public function Query($sql, $key_field = null, $no_debug = false)
{
$conn =& $this->chooseConnection($sql);
return $conn->Query($sql, $key_field, $no_debug);
}
/**
- * Returns iterator to a recordset, produced from running $sql query Queries db with $sql query supplied and returns kMySQLQuery iterator
- * or false in case of error. Optional parameter $key_field allows to
- * set one of the query fields value as key in string array.
+ * Returns iterator to a recordset, produced from running $sql query.
+ *
+ * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
- * @param bool $no_debug
+ * @param bool $no_debug
* @param string $iterator_class
* @return kMySQLQuery|bool
* @access public
*/
public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
$conn =& $this->chooseConnection($sql);
return $conn->GetIterator($sql, $key_field, $no_debug, $iterator_class);
}
/**
- * Performs sql query, that will change database content
+ * Free memory used to hold recordset handle.
+ *
+ * @access public
+ */
+ public function Destroy()
+ {
+ $conn =& $this->openConnection($this->lastUsedIndex);
+
+ $conn->Destroy();
+ }
+
+ /**
+ * Performs sql query, that will change database content.
*
* @param string $sql
* @return bool
* @access public
*/
public function ChangeQuery($sql)
{
$conn =& $this->chooseConnection($sql);
return $conn->ChangeQuery($sql);
}
/**
- * Returns auto increment field value from
- * insert like operation if any, zero otherwise
+ * Returns auto increment field value from insert like operation if any, zero otherwise.
*
* @return int
* @access public
*/
public function getInsertID()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getInsertID();
}
/**
- * Returns row count affected by last query
+ * Returns row count affected by last query.
*
* @return int
* @access public
*/
public function getAffectedRows()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getAffectedRows();
}
/**
- * Returns LIMIT sql clause part for specific db
+ * Returns LIMIT sql clause part for specific db.
*
* @param int $offset
* @param int $rows
* @return string
* @access public
*/
public function getLimitClause($offset, $rows)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getLimitClause($offset, $rows);
}
/**
- * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0)
- * Otherwise returns as-is
+ * If it's a string, adds quotes and backslashes. Otherwise returns as-is.
*
* @param mixed $string
* @return string
* @access public
*/
public function qstr($string)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->qstr($string);
}
/**
- * Calls "qstr" function for each given array element
+ * Calls "qstr" function for each given array element.
*
* @param Array $array
* @param string $function
* @return Array
*/
public function qstrArray($array, $function = 'qstr')
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->qstrArray($array, $function);
}
/**
- * Escapes strings (only work since PHP 4.3.0)
+ * Escapes string.
*
* @param mixed $string
* @return string
* @access public
*/
public function escape($string)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->escape($string);
}
/**
- * Returns last error code occurred
+ * Returns last error code occurred.
*
* @return int
* @access public
*/
public function getErrorCode()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getErrorCode();
}
/**
- * Returns last error message
+ * Returns last error message.
*
* @return string
* @access public
*/
public function getErrorMsg()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getErrorMsg();
}
/**
* Performs insert of given data (useful with small number of queries)
- * or stores it to perform multiple insert later (useful with large number of queries)
+ * or stores it to perform multiple insert later (useful with large number of queries).
*
* @param Array $fields_hash
* @param string $table
* @param string $type
* @param bool $insert_now
* @return bool
* @access public
*/
public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true)
{
$conn =& $this->openConnection( $this->getMasterIndex() );
return $conn->doInsert($fields_hash, $table, $type, $insert_now);
}
/**
- * Update given field values to given record using $key_clause
+ * Update given field values to given record using $key_clause.
*
* @param Array $fields_hash
* @param string $table
* @param string $key_clause
* @return bool
* @access public
*/
public function doUpdate($fields_hash, $table, $key_clause)
{
$conn =& $this->openConnection( $this->getMasterIndex() );
return $conn->doUpdate($fields_hash, $table, $key_clause);
}
/**
- * Allows to detect table's presence in database
+ * Allows to detect table's presence in database.
*
* @param string $table_name
* @param bool $force
* @return bool
* @access public
*/
public function TableFound($table_name, $force = false)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->TableFound($table_name, $force);
}
/**
- * Returns query processing statistics
+ * Returns query processing statistics.
*
* @return Array
* @access public
*/
public function getQueryStatistics()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getQueryStatistics();
}
/**
- * Get status information from SHOW STATUS in an associative array
+ * Get status information from SHOW STATUS in an associative array.
*
* @param string $which
* @return Array
* @access public
*/
public function getStatus($which = '%')
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getStatus($which);
}
/**
* When undefined method is called, then send it directly to last used slave server connection
*
* @param string $name
* @param Array $arguments
* @return mixed
* @access public
*/
public function __call($name, $arguments)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return call_user_func_array( Array (&$conn, $name), $arguments );
}
/**
* Returns appropriate connection based on sql
*
* @param string $sql
* @return kDBConnection
* @access protected
*/
protected function &chooseConnection($sql)
{
if ( $this->nextQueryFromMaster ) {
$this->nextQueryFromMaster = false;
$index = $this->getMasterIndex();
}
else {
$sid = isset($this->Application->Session) ? $this->Application->GetSID() : '9999999999999999999999';
if ( preg_match('/(^[ \t\r\n]*(ALTER|CREATE|DROP|RENAME|DELETE|DO|INSERT|LOAD|REPLACE|TRUNCATE|UPDATE))|ses_' . $sid . '/', $sql) ) {
$index = $this->getMasterIndex();
}
else {
$index = $this->getSlaveIndex();
}
}
$this->lastUsedIndex = $index;
$conn =& $this->openConnection($index);
return $conn;
}
/**
* Get slave replication lag. It will only work if the DB user has the PROCESS privilege.
*
* @return int
* @access public
*/
public function getSlaveLag()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getSlaveLag();
}
}
Index: branches/5.3.x/core/kernel/db/i_db_connection.php
===================================================================
--- branches/5.3.x/core/kernel/db/i_db_connection.php (revision 15921)
+++ branches/5.3.x/core/kernel/db/i_db_connection.php (revision 15922)
@@ -1,161 +1,262 @@
<?php
/**
* @version $Id: i_db_connection.php 15904 2013-07-17 18:52:50Z erik $
* @package In-Portal
* @copyright Copyright (C) 1997 - 2013 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Database connection interface
*
*/
interface IDBConnection {
/**
- * Performs sql query, that will change database content
+ * Checks if previous query execution raised an error.
+ *
+ * @return bool
+ * @access public
+ */
+ public function hasError();
+
+ /**
+ * Checks if connection to database is opened.
+ *
+ * @return bool
+ * @access public
+ */
+ public function connectionOpened();
+
+ /**
+ * Setups the connection according given configuration.
+ *
+ * @param Array $config
+ * @return bool
+ * @access public
+ */
+ public function setup($config);
+
+ /**
+ * Returns first field of first line of recordset if query ok or false otherwise.
+ *
+ * @param string $sql
+ * @param int $offset
+ * @return string
+ * @access public
+ */
+ public function GetOne($sql, $offset = 0);
+
+ /**
+ * Returns first row of recordset if query ok, false otherwise.
+ *
+ * @param string $sql
+ * @param int $offset
+ * @return Array
+ * @access public
+ */
+ public function GetRow($sql, $offset = 0);
+
+ /**
+ * Returns 1st column of recordset as one-dimensional array or false otherwise.
+ *
+ * Optional parameter $key_field can be used to set field name to be used as resulting array key.
+ *
+ * @param string $sql
+ * @param string $key_field
+ * @return Array
+ * @access public
+ */
+ public function GetCol($sql, $key_field = null);
+
+ /**
+ * Returns iterator for 1st column of a recordset or false in case of error.
+ *
+ * Optional parameter $key_field can be used to set field name to be used as resulting array key.
+ *
+ * @param string $sql
+ * @param string $key_field
+ * @return bool|kMySQLQueryCol
+ */
+ public function GetColIterator($sql, $key_field = null);
+
+ /**
+ * Queries db with $sql query supplied and returns rows selected if any, false otherwise.
+ *
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
+ *
+ * @param string $sql
+ * @param string $key_field
+ * @param bool $no_debug
+ * @return Array
+ * @access public
+ */
+ public function Query($sql, $key_field = null, $no_debug = false);
+
+ /**
+ * Returns iterator to a recordset, produced from running $sql query.
+ *
+ * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
+ *
+ * @param string $sql
+ * @param string $key_field
+ * @param bool $no_debug
+ * @param string $iterator_class
+ * @return kMySQLQuery|bool
+ * @access public
+ */
+ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery');
+
+ /**
+ * Free memory used to hold recordset handle.
+ *
+ * @access public
+ */
+ public function Destroy();
+
+ /**
+ * Performs sql query, that will change database content.
*
* @param string $sql
* @return bool
* @access public
*/
public function ChangeQuery($sql);
/**
- * Returns auto increment field value from
- * insert like operation if any, zero otherwise
+ * Returns auto increment field value from insert like operation if any, zero otherwise.
*
* @return int
* @access public
*/
public function getInsertID();
/**
- * Returns row count affected by last query
+ * Returns row count affected by last query.
*
* @return int
* @access public
*/
public function getAffectedRows();
/**
- * Returns LIMIT sql clause part for specific db
+ * Returns LIMIT sql clause part for specific db.
*
* @param int $offset
* @param int $rows
* @return string
* @access public
*/
public function getLimitClause($offset, $rows);
/**
- * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0)
- * Otherwise returns as-is
+ * If it's a string, adds quotes and backslashes. Otherwise returns as-is.
*
* @param mixed $string
* @return string
* @access public
*/
public function qstr($string);
/**
- * Calls "qstr" function for each given array element
+ * Calls "qstr" function for each given array element.
*
* @param Array $array
* @param string $function
* @return Array
*/
public function qstrArray($array, $function = 'qstr');
/**
- * Escapes strings (only work since PHP 4.3.0)
+ * Escapes string.
*
* @param mixed $string
* @return string
* @access public
*/
public function escape($string);
/**
- * Returns last error code occurred
+ * Returns last error code occurred.
*
* @return int
* @access public
*/
public function getErrorCode();
/**
- * Returns last error message
+ * Returns last error message.
*
* @return string
* @access public
*/
public function getErrorMsg();
/**
* Performs insert of given data (useful with small number of queries)
- * or stores it to perform multiple insert later (useful with large number of queries)
+ * or stores it to perform multiple insert later (useful with large number of queries).
*
* @param Array $fields_hash
* @param string $table
* @param string $type
* @param bool $insert_now
* @return bool
* @access public
*/
public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true);
/**
- * Update given field values to given record using $key_clause
+ * Update given field values to given record using $key_clause.
*
* @param Array $fields_hash
* @param string $table
* @param string $key_clause
* @return bool
* @access public
*/
public function doUpdate($fields_hash, $table, $key_clause);
/**
- * Allows to detect table's presence in database
+ * Allows to detect table's presence in database.
*
* @param string $table_name
* @param bool $force
* @return bool
* @access public
*/
public function TableFound($table_name, $force = false);
/**
- * Returns query processing statistics
+ * Returns query processing statistics.
*
* @return Array
* @access public
*/
public function getQueryStatistics();
/**
- * Get status information from SHOW STATUS in an associative array
+ * Get status information from SHOW STATUS in an associative array.
*
* @param string $which
* @return Array
* @access public
*/
public function getStatus($which = '%');
/**
* Get slave replication lag. It will only work if the DB user has the PROCESS privilege.
*
* @return int
* @access public
*/
public function getSlaveLag();
}
Index: branches/5.3.x/core/kernel/db/db_connection.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_connection.php (revision 15921)
+++ branches/5.3.x/core/kernel/db/db_connection.php (revision 15922)
@@ -1,1518 +1,1507 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Multi database connection class
*
*/
class kDBConnection extends kBase implements IDBConnection {
/**
* Current database type
*
* @var string
* @access protected
*/
protected $dbType = 'mysql';
/**
* Created connection handle
*
* @var resource
* @access protected
*/
protected $connectionID = null;
/**
* Remembers, that database connection was opened successfully
*
* @var bool
* @access public
*/
public $connectionOpened = false;
/**
* Connection parameters, that were used
*
* @var Array
* @access protected
*/
protected $connectionParams = Array ('host' => '', 'user' => '', 'pass' => '', 'db' => '');
/**
* Index of database server
*
* @var int
* @access protected
*/
protected $serverIndex = 0;
/**
* Handle of currently processed recordset
*
* @var resource
* @access protected
*/
protected $queryID = null;
/**
* DB type specific function mappings
*
* @var Array
* @access protected
*/
protected $metaFunctions = Array ();
/**
* Function to handle sql errors
*
* @var Array|string
* @access public
*/
public $errorHandler = '';
/**
* Error code
*
* @var int
* @access protected
*/
protected $errorCode = 0;
/**
* Error message
*
* @var string
* @access protected
*/
protected $errorMessage = '';
/**
* Defines if database connection
* operations should generate debug
* information
*
* @var bool
* @access public
*/
public $debugMode = false;
/**
* Save query execution statistics
*
* @var bool
* @access protected
*/
protected $_captureStatistics = false;
/**
* Last query to database
*
* @var string
* @access public
*/
public $lastQuery = '';
/**
* Total processed queries count
*
* @var int
* @access protected
*/
protected $_queryCount = 0;
/**
* Total time, used for serving queries
*
* @var Array
* @access protected
*/
protected $_queryTime = 0;
/**
* Indicates, that next database query could be cached, when memory caching is enabled
*
* @var bool
* @access public
*/
public $nextQueryCachable = false;
/**
* For backwards compatibility with kDBLoadBalancer class
*
* @var bool
* @access public
*/
public $nextQueryFromMaster = false;
/**
* Initializes connection class with
* db type to used in future
*
* @param string $dbType
* @param string $errorHandler
* @param int $server_index
* @access public
*/
public function __construct($dbType, $errorHandler = '', $server_index = 0)
{
if ( class_exists('kApplication') ) {
// prevents "Fatal Error" on 2nd installation step (when database is empty)
parent::__construct();
}
$this->dbType = $dbType;
$this->serverIndex = $server_index;
// $this->initMetaFunctions();
if (!$errorHandler) {
$this->errorHandler = Array(&$this, 'handleError');
}
else {
$this->errorHandler = $errorHandler;
}
$this->_captureStatistics = defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !(defined('ADMIN') && ADMIN);
}
/**
* Set's custom error
*
* @param int $code
* @param string $msg
* @access protected
*/
protected function setError($code, $msg)
{
$this->errorCode = $code;
$this->errorMessage = $msg;
}
/**
- * Checks if previous query execution
- * raised an error.
+ * Checks if previous query execution raised an error.
*
* @return bool
* @access public
*/
public function hasError()
{
return $this->errorCode != 0;
}
/**
* Caches function specific to requested
* db type
*
* @access protected
*/
protected function initMetaFunctions()
{
$ret = Array ();
switch ( $this->dbType ) {
case 'mysql':
$ret = Array (); // only define functions, that name differs from "dbType_<meta_name>"
break;
}
$this->metaFunctions = $ret;
}
/**
* Gets function for specific db type
* based on it's meta name
*
* @param string $name
* @return string
* @access protected
*/
protected function getMetaFunction($name)
{
/*if ( !isset($this->metaFunctions[$name]) ) {
$this->metaFunctions[$name] = $name;
}*/
return $this->dbType . '_' . $name;
}
-
/**
- * Try to connect to database server
- * using specified parameters and set
- * database to $db if connection made
+ * Try to connect to database server using specified parameters and set database to $db if connection made.
*
* @param string $host
* @param string $user
* @param string $pass
* @param string $db
- * @param bool $force_new
- * @param bool $retry
+ * @param bool $force_new
+ * @param bool $retry
+ *
* @return bool
* @access public
+ * @throws RuntimeException When connection failed.
*/
public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false)
{
$this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db);
$func = $this->getMetaFunction('connect');
$this->connectionID = $func($host, $user, $pass, $force_new);
if ($this->connectionID) {
if (defined('DBG_SQL_MODE')) {
$this->Query('SET sql_mode = \''.DBG_SQL_MODE.'\'');
}
if (defined('SQL_COLLATION') && defined('SQL_CHARSET')) {
$this->Query('SET NAMES \''.SQL_CHARSET.'\' COLLATE \''.SQL_COLLATION.'\'');
}
$this->setError(0, ''); // reset error
$this->setDB($db);
}
// process error (fatal in most cases)
$func = $this->getMetaFunction('errno');
$this->errorCode = $this->connectionID ? $func($this->connectionID) : $func();
if ( is_resource($this->connectionID) && !$this->hasError() ) {
$this->connectionOpened = true;
return true;
}
$func = $this->getMetaFunction('error');
$this->errorMessage = $this->connectionID ? $func($this->connectionID) : $func();
$error_msg = 'Database connection failed, please check your connection settings.<br/>Error (' . $this->errorCode . '): ' . $this->errorMessage;
if ( (defined('IS_INSTALL') && IS_INSTALL) || $retry ) {
trigger_error($error_msg, E_USER_WARNING);
}
else {
$this->Application->redirectToMaintenance();
throw new RuntimeException($error_msg);
}
$this->connectionOpened = false;
return false;
}
/**
- * Checks if connection to database is opened
+ * Checks if connection to database is opened.
*
* @return bool
* @access public
*/
public function connectionOpened()
{
return $this->connectionOpened;
}
/**
- * Setups the connection according given configuration
+ * Setups the connection according given configuration.
*
* @param Array $config
* @return bool
* @access public
*/
public function setup($config)
{
if ( is_object($this->Application) ) {
$this->debugMode = $this->Application->isDebugMode();
}
return $this->Connect(
$config['Database']['DBHost'],
$config['Database']['DBUser'],
$config['Database']['DBUserPassword'],
$config['Database']['DBName']
);
}
/**
* Performs 3 reconnect attempts in case if connection to a DB was lost in the middle of script run (e.g. server restart)
*
* @param bool $force_new
* @return bool
* @access protected
*/
protected function ReConnect($force_new = false)
{
$retry_count = 0;
$connected = false;
$func = $this->getMetaFunction('close');
$func($this->connectionID);
while ( $retry_count < 3 ) {
sleep(5); // wait 5 seconds before each reconnect attempt
$connected = $this->Connect(
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['pass'],
$this->connectionParams['db'],
$force_new, true
);
if ( $connected ) {
break;
}
$retry_count++;
}
return $connected;
}
/**
* Shows error message from previous operation
* if it failed
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return bool
* @access protected
*/
protected function showError($sql = '', $key_field = null, $no_debug = false)
{
static $retry_count = 0;
$func = $this->getMetaFunction('errno');
if (!$this->connectionID) {
// no connection while doing mysql_query
$this->errorCode = $func();
if ( $this->hasError() ) {
$func = $this->getMetaFunction('error');
$this->errorMessage = $func();
$ret = $this->callErrorHandler($sql);
if (!$ret) {
exit;
}
}
return false;
}
// checking if there was an error during last mysql_query
$this->errorCode = $func($this->connectionID);
if ( $this->hasError() ) {
$func = $this->getMetaFunction('error');
$this->errorMessage = $func($this->connectionID);
$ret = $this->callErrorHandler($sql);
if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && ($retry_count < 3) ) {
// #2006 - MySQL server has gone away
// #2013 - Lost connection to MySQL server during query
$retry_count++;
if ( $this->ReConnect() ) {
return $this->Query($sql, $key_field, $no_debug);
}
}
if (!$ret) {
exit;
}
}
else {
$retry_count = 0;
}
return false;
}
/**
* Sends db error to a predefined error handler
*
* @param $sql
* @return bool
* @access protected
*/
protected function callErrorHandler($sql)
{
if (is_array($this->errorHandler)) {
$func = $this->errorHandler[1];
$ret = $this->errorHandler[0]->$func($this->errorCode, $this->errorMessage, $sql);
}
else {
$func = $this->errorHandler;
$ret = $func($this->errorCode, $this->errorMessage, $sql);
}
return $ret;
}
/**
* Default error handler for sql errors
*
* @param int $code
* @param string $msg
* @param string $sql
* @return bool
* @access public
*/
public function handleError($code, $msg, $sql)
{
echo '<strong>Processing SQL</strong>: ' . $sql . '<br/>';
echo '<strong>Error (' . $code . '):</strong> ' . $msg . '<br/>';
return false;
}
/**
* Set's database name for connection
* to $new_name
*
* @param string $new_name
* @return bool
* @access protected
*/
protected function setDB($new_name)
{
if (!$this->connectionID) return false;
$func = $this->getMetaFunction('select_db');
return $func($new_name, $this->connectionID);
}
/**
- * Returns first field of first line
- * of recordset if query ok or false
- * otherwise
+ * Returns first field of first line of recordset if query ok or false otherwise.
*
* @param string $sql
* @param int $offset
* @return string
* @access public
*/
public function GetOne($sql, $offset = 0)
{
$row = $this->GetRow($sql, $offset);
if ( !$row ) {
return false;
}
return array_shift($row);
}
/**
- * Returns first row of recordset
- * if query ok, false otherwise
+ * Returns first row of recordset if query ok, false otherwise.
*
* @param string $sql
* @param int $offset
* @return Array
* @access public
*/
public function GetRow($sql, $offset = 0)
{
$sql .= ' ' . $this->getLimitClause($offset, 1);
$ret = $this->Query($sql);
if ( !$ret ) {
return false;
}
return array_shift($ret);
}
/**
- * Returns 1st column of recordset as
- * one-dimensional array or false otherwise
- * Optional parameter $key_field can be used
- * to set field name to be used as resulting
- * array key
+ * Returns 1st column of recordset as one-dimensional array or false otherwise.
+ *
+ * Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return Array
* @access public
*/
public function GetCol($sql, $key_field = null)
{
$rows = $this->Query($sql);
if ( !$rows ) {
return $rows;
}
$i = 0;
$row_count = count($rows);
$ret = Array ();
if ( isset($key_field) ) {
while ( $i < $row_count ) {
$ret[$rows[$i][$key_field]] = array_shift($rows[$i]);
$i++;
}
}
else {
while ( $i < $row_count ) {
$ret[] = array_shift($rows[$i]);
$i++;
}
}
return $ret;
}
/**
* Returns iterator for 1st column of a recordset or false in case of error.
- * Optional parameter $key_field can be used to set field name to be used
- * as resulting array key.
+ *
+ * Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return bool|kMySQLQueryCol
*/
public function GetColIterator($sql, $key_field = null)
{
return $this->GetIterator($sql, $key_field, false, 'kMySQLQueryCol');
}
/**
- * Queries db with $sql query supplied
- * and returns rows selected if any, false
- * otherwise. Optional parameter $key_field
- * allows to set one of the query fields
- * value as key in string array.
+ * Queries db with $sql query supplied and returns rows selected if any, false otherwise.
+ *
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return Array
* @access public
*/
public function Query($sql, $key_field = null, $no_debug = false)
{
$this->_queryCount++;
$this->lastQuery = $sql;
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
$start_time = $this->_captureStatistics ? microtime(true) : 0;
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
$this->queryID = $query_func($sql, $this->connectionID);
if ( is_resource($this->queryID) ) {
$ret = Array ();
$fetch_func = $this->getMetaFunction('fetch_assoc');
if ( isset($key_field) ) {
while (($row = $fetch_func($this->queryID))) {
$ret[$row[$key_field]] = $row;
}
}
else {
while (($row = $fetch_func($this->queryID))) {
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$query_time = microtime(true) - $start_time;
if ( $query_time > DBG_MAX_SQL_TIME ) {
$this->Application->logSlowQuery($sql, $query_time);
}
$this->_queryTime += $query_time;
}
// set 2nd checkpoint: end
$this->Destroy();
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$this->_queryTime += microtime(true) - $start_time;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field, $no_debug);
}
/**
- * Returns iterator to a recordset, produced from running $sql query Queries db with $sql query supplied and returns kMySQLQuery iterator
- * or false in case of error. Optional parameter $key_field allows to
- * set one of the query fields value as key in string array.
+ * Returns iterator to a recordset, produced from running $sql query.
+ *
+ * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
- * @param bool $no_debug
+ * @param bool $no_debug
* @param string $iterator_class
* @return kMySQLQuery|bool
* @access public
*/
public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
$this->_queryCount++;
$this->lastQuery = $sql;
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
$start_time = $this->_captureStatistics ? microtime(true) : 0;
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
$this->queryID = $query_func($sql, $this->connectionID);
if ( is_resource($this->queryID) ) {
$ret = new $iterator_class($this->queryID, $key_field);
/* @var $ret kMySQLQuery */
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$query_time = microtime(true) - $start_time;
if ( $query_time > DBG_MAX_SQL_TIME ) {
$this->Application->logSlowQuery($sql, $query_time);
}
$this->_queryTime += $query_time;
}
// set 2nd checkpoint: end
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$this->_queryTime += microtime(true) - $start_time;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field, $no_debug);
}
/**
- * Free memory used to hold recordset handle
+ * Free memory used to hold recordset handle.
*
* @access public
*/
public function Destroy()
{
$free_func = $this->getMetaFunction('free_result');
$free_func($this->queryID);
unset($this->queryID);
}
/**
- * Performs sql query, that will change database content
+ * Performs sql query, that will change database content.
*
* @param string $sql
* @return bool
* @access public
*/
public function ChangeQuery($sql)
{
$this->Query($sql);
return !$this->hasError();
}
/**
- * Returns auto increment field value from
- * insert like operation if any, zero otherwise
+ * Returns auto increment field value from insert like operation if any, zero otherwise.
*
* @return int
* @access public
*/
public function getInsertID()
{
$func = $this->getMetaFunction('insert_id');
return $func($this->connectionID);
}
/**
- * Returns row count affected by last query
+ * Returns row count affected by last query.
*
* @return int
* @access public
*/
public function getAffectedRows()
{
$func = $this->getMetaFunction('affected_rows');
return $func($this->connectionID);
}
/**
- * Returns LIMIT sql clause part for specific db
+ * Returns LIMIT sql clause part for specific db.
*
* @param int $offset
* @param int $rows
* @return string
* @access public
*/
public function getLimitClause($offset, $rows)
{
if ( !($rows > 0) ) {
return '';
}
switch ( $this->dbType ) {
default:
return 'LIMIT ' . $offset . ',' . $rows;
break;
}
}
/**
- * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0)
- * Otherwise returns as-is
+ * If it's a string, adds quotes and backslashes. Otherwise returns as-is.
*
* @param mixed $string
* @return string
* @access public
*/
public function qstr($string)
{
if ( is_null($string) ) {
return 'NULL';
}
# This will also quote numeric values. This should be harmless,
# and protects against weird problems that occur when they really
# _are_ strings such as article titles and string->number->string
# conversion is not 1:1.
return "'" . mysql_real_escape_string($string, $this->connectionID) . "'";
}
/**
- * Calls "qstr" function for each given array element
+ * Calls "qstr" function for each given array element.
*
* @param Array $array
* @param string $function
* @return Array
*/
public function qstrArray($array, $function = 'qstr')
{
return array_map(Array (&$this, $function), $array);
}
/**
- * Escapes strings (only work since PHP 4.3.0)
+ * Escapes string.
*
* @param mixed $string
* @return string
* @access public
*/
public function escape($string)
{
if ( is_null($string) ) {
return 'NULL';
}
$string = mysql_real_escape_string($string, $this->connectionID);
// prevent double-escaping of MySQL wildcard symbols ("%" and "_") in case if they were already escaped
return str_replace(Array ('\\\\%', '\\\\_'), Array ('\\%', '\\_'), $string);
}
/**
- * Returns last error code occurred
+ * Returns last error code occurred.
*
* @return int
* @access public
*/
public function getErrorCode()
{
return $this->errorCode;
}
/**
- * Returns last error message
+ * Returns last error message.
*
* @return string
* @access public
*/
public function getErrorMsg()
{
return $this->errorMessage;
}
/**
* Performs insert of given data (useful with small number of queries)
- * or stores it to perform multiple insert later (useful with large number of queries)
+ * or stores it to perform multiple insert later (useful with large number of queries).
*
* @param Array $fields_hash
* @param string $table
* @param string $type
* @param bool $insert_now
* @return bool
* @access public
*/
public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true)
{
static $value_sqls = Array ();
if ($insert_now) {
$fields_sql = '`' . implode('`,`', array_keys($fields_hash)) . '`';
}
$values_sql = '';
foreach ($fields_hash as $field_name => $field_value) {
$values_sql .= $this->qstr($field_value) . ',';
}
// don't use preg here, as it may fail when string is too long
$value_sqls[] = rtrim($values_sql, ',');
$insert_result = true;
if ($insert_now) {
$insert_count = count($value_sqls);
if (($insert_count > 1) && ($value_sqls[$insert_count - 1] == $value_sqls[$insert_count - 2])) {
// last two records are the same
array_pop($value_sqls);
}
$sql = strtoupper($type) . ' INTO `' . $table . '` (' . $fields_sql . ') VALUES (' . implode('),(', $value_sqls) . ')';
$value_sqls = Array (); // reset before query to prevent repeated call from error handler to insert 2 records instead of 1
$insert_result = $this->ChangeQuery($sql);
}
return $insert_result;
}
/**
- * Update given field values to given record using $key_clause
+ * Update given field values to given record using $key_clause.
*
* @param Array $fields_hash
* @param string $table
* @param string $key_clause
* @return bool
* @access public
*/
public function doUpdate($fields_hash, $table, $key_clause)
{
if (!$fields_hash) return true;
$fields_sql = '';
foreach ($fields_hash as $field_name => $field_value) {
$fields_sql .= '`'.$field_name.'` = ' . $this->qstr($field_value) . ',';
}
// don't use preg here, as it may fail when string is too long
$fields_sql = rtrim($fields_sql, ',');
$sql = 'UPDATE `'.$table.'` SET '.$fields_sql.' WHERE '.$key_clause;
return $this->ChangeQuery($sql);
}
/**
- * Allows to detect table's presence in database
+ * Allows to detect table's presence in database.
*
* @param string $table_name
* @param bool $force
* @return bool
* @access public
*/
public function TableFound($table_name, $force = false)
{
static $table_found = false;
if ( $table_found === false ) {
$table_found = array_flip($this->GetCol('SHOW TABLES'));
}
if ( !preg_match('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', $table_name) ) {
$table_name = TABLE_PREFIX . $table_name;
}
if ( $force ) {
if ( $this->Query('SHOW TABLES LIKE ' . $this->qstr($table_name)) ) {
$table_found[$table_name] = 1;
}
else {
unset($table_found[$table_name]);
}
}
return isset($table_found[$table_name]);
}
/**
- * Returns query processing statistics
+ * Returns query processing statistics.
*
* @return Array
* @access public
*/
public function getQueryStatistics()
{
return Array ('time' => $this->_queryTime, 'count' => $this->_queryCount);
}
/**
- * Get status information from SHOW STATUS in an associative array
+ * Get status information from SHOW STATUS in an associative array.
*
* @param string $which
* @return Array
* @access public
*/
public function getStatus($which = '%')
{
$status = Array ();
$records = $this->Query('SHOW STATUS LIKE "' . $which . '"');
foreach ($records as $record) {
$status[ $record['Variable_name'] ] = $record['Value'];
}
return $status;
}
/**
* Get slave replication lag. It will only work if the DB user has the PROCESS privilege.
*
* @return int
* @access public
*/
public function getSlaveLag()
{
// don't use kDBConnection::Query method, since it will create an array of all server processes
$processes = $this->GetIterator('SHOW PROCESSLIST');
$skip_states = Array (
'Waiting for master to send event',
'Connecting to master',
'Queueing master event to the relay log',
'Waiting for master update',
'Requesting binlog dump',
);
// find slave SQL thread
foreach ($processes as $process) {
if ( $process['User'] == 'system user' && !in_array($process['State'], $skip_states) ) {
// this is it, return the time (except -ve)
return $process['Time'] > 0x7fffffff ? false : $process['Time'];
}
}
return false;
}
}
class kDBConnectionDebug extends kDBConnection {
protected $_profileSQLs = false;
/**
* Info about this database connection to show in debugger report
*
* @var string
* @access protected
*/
protected $serverInfoLine = '';
/**
* Initializes connection class with
* db type to used in future
*
* @param string $dbType
* @param string $errorHandler
* @param int $server_index
* @access public
*/
public function __construct($dbType, $errorHandler = '', $server_index = 0)
{
parent::__construct($dbType, $errorHandler, $server_index);
$this->_profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE;
}
/**
* Try to connect to database server
* using specified parameters and set
* database to $db if connection made
*
* @param string $host
* @param string $user
* @param string $pass
* @param string $db
* @param bool $force_new
* @param bool $retry
* @return bool
* @access public
*/
public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false)
{
if ( defined('DBG_SQL_SERVERINFO') && DBG_SQL_SERVERINFO ) {
$this->serverInfoLine = $this->serverIndex . ' (' . $host . ')';
}
return parent::Connect($host, $user, $pass, $db, $force_new, $retry);
}
/**
- * Queries db with $sql query supplied
- * and returns rows selected if any, false
- * otherwise. Optional parameter $key_field
- * allows to set one of the query fields
- * value as key in string array.
+ * Queries db with $sql query supplied and returns rows selected if any, false otherwise.
+ *
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return Array
* @access public
*/
public function Query($sql, $key_field = null, $no_debug = false)
{
if ( $no_debug ) {
return parent::Query($sql, $key_field, $no_debug);
}
global $debugger;
$this->_queryCount++;
$this->lastQuery = $sql;
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
if ( $this->_profileSQLs ) {
$queryID = $debugger->generateID();
$debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql));
}
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
$this->queryID = $query_func($sql, $this->connectionID);
if ( is_resource($this->queryID) ) {
$ret = Array ();
$fetch_func = $this->getMetaFunction('fetch_assoc');
if ( isset($key_field) ) {
while (($row = $fetch_func($this->queryID))) {
$ret[$row[$key_field]] = $row;
}
}
else {
while (($row = $fetch_func($this->queryID))) {
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$current_element = current($ret);
$first_cell = count($ret) == 1 && count($current_element) == 1 ? current($current_element) : null;
if ( strlen($first_cell) > 200 ) {
$first_cell = substr($first_cell, 0, 50) . ' ...';
}
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
$this->Destroy();
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field);
}
/**
- * Queries db with $sql query supplied and returns kMySQLQuery iterator
- * or false in case of error. Optional parameter $key_field allows to
- * set one of the query fields value as key in string array.
+ * Returns iterator to a recordset, produced from running $sql query.
+ *
+ * Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
+ * Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
- * @param bool $no_debug
+ * @param bool $no_debug
* @param string $iterator_class
* @return kMySQLQuery|bool
* @access public
*/
public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
if ( $no_debug ) {
return parent::Query($sql, $key_field, $no_debug, $iterator_class);
}
global $debugger;
$this->_queryCount++;
$this->lastQuery = $sql;
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
if ( $this->_profileSQLs ) {
$queryID = $debugger->generateID();
$debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql));
}
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
$this->queryID = $query_func($sql, $this->connectionID);
if ( is_resource($this->queryID) ) {
$ret = new $iterator_class($this->queryID, $key_field);
/* @var $ret kMySQLQuery */
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$first_cell = count($ret) == 1 && $ret->fieldCount() == 1 ? current($ret->current()) : null;
if ( strlen($first_cell) > 200 ) {
$first_cell = substr($first_cell, 0, 50) . ' ...';
}
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field);
}
}
class kMySQLQuery implements Iterator, Countable, SeekableIterator {
/**
* Current index in recordset
*
* @var int
* @access protected
*/
protected $position = -1;
/**
* Query resource
*
* @var resource
* @access protected
*/
protected $result;
/**
* Field to act as key in a resulting array
*
* @var string
* @access protected
*/
protected $keyField = null;
/**
* Data in current row of recordset
*
* @var Array
* @access protected
*/
protected $rowData = Array ();
/**
* Row count in a result
*
* @var int
* @access protected
*/
protected $rowCount = 0;
/**
* Creates new instance of a class
*
* @param resource $result
* @param null|string $key_field
*/
public function __construct($result, $key_field = null)
{
$this->result = $result;
$this->keyField = $key_field;
$this->rowCount = mysql_num_rows($this->result);
$this->rewind();
}
/**
* Moves recordset pointer to first element
*
* @return void
* @access public
* @implements Iterator::rewind
*/
public function rewind()
{
$this->seek(0);
}
/**
* Returns value at current position
*
* @return mixed
* @access public
* @implements Iterator::current
*/
function current()
{
return $this->rowData;
}
/**
* Returns key at current position
*
* @return mixed
* @access public
* @implements Iterator::key
*/
function key()
{
return $this->keyField ? $this->rowData[$this->keyField] : $this->position;
}
/**
* Moves recordset pointer to next position
*
* @return void
* @access public
* @implements Iterator::next
*/
function next()
{
$this->seek($this->position + 1);
}
/**
* Detects if current position is within recordset bounds
*
* @return bool
* @access public
* @implements Iterator::valid
*/
public function valid()
{
return $this->position < $this->rowCount;
}
/**
* Counts recordset rows
*
* @return int
* @access public
* @implements Countable::count
*/
public function count()
{
return $this->rowCount;
}
/**
* Counts fields in current row
*
* @return int
* @access public
*/
public function fieldCount()
{
return count($this->rowData);
}
/**
* Moves cursor into given position within recordset
*
* @param int $position
* @throws OutOfBoundsException
* @access public
* @implements SeekableIterator::seek
*/
public function seek($position)
{
if ( $this->position == $position ) {
return;
}
$this->position = $position;
if ( $this->valid() ) {
mysql_data_seek($this->result, $this->position);
$this->rowData = mysql_fetch_assoc($this->result);
}
/*if ( !$this->valid() ) {
throw new OutOfBoundsException('Invalid seek position (' . $position . ')');
}*/
}
/**
* Returns first recordset row
*
* @return Array
* @access public
*/
public function first()
{
$this->seek(0);
return $this->rowData;
}
/**
* Closes recordset and freese memory
*
* @return void
* @access public
*/
public function close()
{
mysql_free_result($this->result);
unset($this->result);
}
/**
* Frees memory when object is destroyed
*
* @return void
* @access public
*/
public function __destruct()
{
$this->close();
}
/**
* Returns all keys
*
* @return Array
* @access public
*/
public function keys()
{
$ret = Array ();
foreach ($this as $key => $value) {
$ret[] = $key;
}
return $ret;
}
/**
* Returns all values
*
* @return Array
* @access public
*/
public function values()
{
$ret = Array ();
foreach ($this as $value) {
$ret[] = $value;
}
return $ret;
}
/**
* Returns whole recordset as array
*
* @return Array
* @access public
*/
public function toArray()
{
$ret = Array ();
foreach ($this as $key => $value) {
$ret[$key] = $value;
}
return $ret;
}
}
class kMySQLQueryCol extends kMySQLQuery {
/**
* Returns value at current position
*
* @return mixed
* @access public
* @implements Iterator::current
*/
function current()
{
return reset($this->rowData);
}
/**
* Returns first column of first recordset row
*
* @return string
* @access public
*/
public function first()
{
$this->seek(0);
return reset($this->rowData);
}
}
Event Timeline
Log In to Comment