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