Index: branches/5.2.x/core/kernel/db/db_connection.php
===================================================================
--- branches/5.2.x/core/kernel/db/db_connection.php	(revision 16652)
+++ branches/5.2.x/core/kernel/db/db_connection.php	(revision 16653)
@@ -1,1408 +1,1405 @@
 <?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 {
 
 		/**
 		 * Created connection handle
 		 *
 		 * @var mysqli
 		 * @access protected
 		 */
 		protected $connectionID;
 
 		/**
 		 * 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 mysqli_result
 		 * @access protected
 		 */
 		protected $queryID = null;
 
 		/**
 		 * 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 $db_type
 		 * @param string $error_handler
 		 * @param int $server_index
 		 * @access public
 		 */
 		public function __construct($db_type, $error_handler = '', $server_index = 0)
 		{
 			if ( class_exists('kApplication') ) {
 				// prevents "Fatal Error" on 2nd installation step (when database is empty)
 				parent::__construct();
 			}
 
 			$this->serverIndex = $server_index;
 
 			if ( !$error_handler ) {
 				$this->errorHandler = Array(&$this, 'handleError');
 			}
 			else {
 				$this->errorHandler = $error_handler;
 			}
 
 			$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.
 		 *
 		 * @return bool
 		 * @access public
 		 */
 		public function hasError()
 		{
 			return $this->errorCode != 0;
 		}
 
 		/**
 		 * 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   $retry
 		 *
 		 * @return bool
 		 * @access public
 		 * @throws RuntimeException When connection failed.
 		 */
 		public function Connect($host, $user, $pass, $db, $retry = false)
 		{
 			$this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db);
 
 			$this->setError(0, ''); // reset error
 			$this->connectionID = mysqli_connect($host, $user, $pass, $db);
 			$this->errorCode = mysqli_connect_errno();
 
 			if ( is_object($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 . '\'');
 				}
 
 				if ( !$this->hasError() ) {
 					$this->connectionOpened = true;
 
 					return true;
 				}
 			}
 
 			$this->errorMessage = mysqli_connect_error();
 			$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.
 		 *
 		 * @return bool
 		 * @access public
 		 */
 		public function connectionOpened()
 		{
 			return $this->connectionOpened;
 		}
 
 		/**
 		 * 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)
 		 *
 		 * @return bool
 		 * @access protected
 		 */
 		protected function ReConnect()
 		{
 			$retry_count = 0;
 			$connected = false;
 
 			$this->connectionID->close();
 
 			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'],
 					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;
 
 			if ( !is_object($this->connectionID) ) {
 				// no connection while doing mysql_query
 				$this->errorCode = mysqli_connect_errno();
 
 				if ( $this->hasError() ) {
 					$this->errorMessage = mysqli_connect_error();
 
 					$ret = $this->callErrorHandler($sql);
 
 					if (!$ret) {
 						exit;
 					}
 				}
 
 				return false;
 			}
 
 			// checking if there was an error during last mysql_query
 			$this->errorCode = $this->connectionID->errno;
 
 			if ( $this->hasError() ) {
 				$this->errorMessage = $this->connectionID->error;
 
 				$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)
 		{
 			return call_user_func($this->errorHandler, $this->errorCode, $this->errorMessage, $sql);
 		}
 
 		/**
 		 * 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;
 		}
 
 		/**
 		 * 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.
 		 *
 		 * @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.
 		 *
 		 * @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.
 		 *
 		 * @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.
 		 *
 		 * @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;
 
 			// set 1st checkpoint: begin
 			$start_time = $this->_captureStatistics ? microtime(true) : 0;
 			// set 1st checkpoint: end
 
 			$this->setError(0, ''); // reset error
 			$this->queryID = $this->connectionID->query($sql);
 
 			if ( is_object($this->queryID) ) {
 				$ret = Array ();
 
 				if ( isset($key_field) ) {
 					while ( $row = $this->queryID->fetch_assoc() ) {
 						$ret[$row[$key_field]] = $row;
 					}
 				}
 				else {
 					while ( $row = $this->queryID->fetch_assoc() ) {
 						$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.
 		 *
 		 * @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')
 		{
 			$this->_queryCount++;
 			$this->lastQuery = $sql;
 
 			// set 1st checkpoint: begin
 			$start_time = $this->_captureStatistics ? microtime(true) : 0;
 			// set 1st checkpoint: end
 
 			$this->setError(0, ''); // reset error
 			$this->queryID = $this->connectionID->query($sql);
 
 			if ( is_object($this->queryID) ) {
 				/** @var kMySQLQuery $ret */
 				$ret = new $iterator_class($this->queryID, $key_field);
 
 				// 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.
 		 *
 		 * @access public
 		 */
 		public function Destroy()
 		{
 			$this->queryID->free();
 			unset($this->queryID);
 		}
 
 		/**
 		 * 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.
 		 *
 		 * @return int
 		 * @access public
 		 */
 		public function getInsertID()
 		{
 			return $this->connectionID->insert_id;
 		}
 
 		/**
 		 * Returns row count affected by last query.
 		 *
 		 * @return int
 		 * @access public
 		 */
 		public function getAffectedRows()
 		{
 			return $this->connectionID->affected_rows;
 		}
 
 		/**
 		 * 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 '';
 			}
 
 			return 'LIMIT ' . $offset . ',' . $rows;
 		}
 
 		/**
 		 * 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 "'" . $this->connectionID->real_escape_string($string) . "'";
 		}
 
 		/**
 		 * 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 string.
 		 *
 		 * @param mixed $string
 		 * @return string
 		 * @access public
 		 */
 		public function escape($string)
 		{
 			if ( is_null($string) ) {
 				return 'NULL';
 			}
 
 			$string = $this->connectionID->real_escape_string($string);
 
 			// 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.
 		 *
 		 * @return int
 		 * @access public
 		 */
 		public function getErrorCode()
 		{
 			return $this->errorCode;
 		}
 
 		/**
 		 * 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).
 		 *
 		 * @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.
 		 *
 		 * @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.
 		 *
 		 * @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.
 		 *
 		 * @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.
 		 *
 		 * @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)
+			try {
+				$rows = $this->Query('SHOW SLAVE STATUS');
+			}
+			catch ( RuntimeException $e ) {
+				// When "SUPER" or "REPLICATION CLIENT" permission is missing.
+				return 0;
+			}
 
-					return $process['Time'] > 0x7fffffff ? false : $process['Time'];
-				}
+			// On the silenced error OR database server isn't configured for a replication.
+			if ( $rows === false || count($rows) !== 1 ) {
+				return 0;
 			}
 
-			return false;
+			$row = reset($rows);
+
+			// When slave is too busy catching up with a master we'll get a NULL/empty string here.
+			return is_numeric($row['Seconds_Behind_Master']) ? $row['Seconds_Behind_Master'] : 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 $db_type
 	 * @param string $error_handler
 	 * @param int $server_index
 	 * @access public
 	 */
 	public function __construct($db_type, $error_handler = '', $server_index = 0)
 	{
 		parent::__construct($db_type, $error_handler, $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.
 	 *
 	 * @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;
 
 		// 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 = $this->connectionID->query($sql);
 
 		if ( is_object($this->queryID) ) {
 			$ret = Array ();
 
 			if ( isset($key_field) ) {
 				while ( $row = $this->queryID->fetch_assoc() ) {
 					$ret[$row[$key_field]] = $row;
 				}
 			}
 			else {
 				while ( $row = $this->queryID->fetch_assoc() ) {
 					$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);
 	}
 
 	/**
 	 * 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')
 	{
 		if ( $no_debug ) {
 			return parent::Query($sql, $key_field, $no_debug, $iterator_class);
 		}
 
 		global $debugger;
 
 		$this->_queryCount++;
 		$this->lastQuery = $sql;
 
 		// 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 = $this->connectionID->query($sql);
 
 		if ( is_object($this->queryID) ) {
 			/** @var kMySQLQuery $ret */
 			$ret = new $iterator_class($this->queryID, $key_field);
 
 			// set 2nd checkpoint: begin
 			if ( $this->_profileSQLs ) {
 				$current_row = $ret->current();
 
 				if ( count($ret) == 1 && $ret->fieldCount() == 1 ) {
 					if ( is_array($current_row) ) {
 						$first_cell = current($current_row);
 					}
 					else {
 						$first_cell = $current_row;
 					}
 				}
 				else {
 					$first_cell = 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 mysqli_result
 	 * @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 mysqli_result $result
 	 * @param null|string $key_field
 	 */
 	public function __construct(mysqli_result $result, $key_field = null)
 	{
 		$this->result = $result;
 		$this->keyField = $key_field;
 
 		$this->rowCount = $this->result->num_rows;
 		$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() ) {
 			$this->result->data_seek($this->position);
 
 			$this->rowData = $this->result->fetch_assoc();
 		}
 
 		/*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()
 	{
 		$this->result->free();
 		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);
 	}
 }