Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sat, Feb 22, 12:03 AM

in-portal

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