Index: branches/5.2.x/core/units/helpers/language_import_helper.php =================================================================== diff -u -N -r15238 -r15239 --- branches/5.2.x/core/units/helpers/language_import_helper.php (.../language_import_helper.php) (revision 15238) +++ branches/5.2.x/core/units/helpers/language_import_helper.php (.../language_import_helper.php) (revision 15239) @@ -1,6 +1,6 @@ Application->getUnitOption($prefix, 'TableName') . ' WHERE ' . $this->Application->getUnitOption($prefix, 'IDField') . ' IN (' . $ids . ')'; - $rs = $this->Conn->QueryRaw($sql); + $rows = $this->Conn->GetIterator($sql); - if ( $this->Conn->RowCount($rs) ) { + if ( count($rows) ) { $data = ''; - while ( ($row = $this->Conn->GetNextRow($rs)) ) { + foreach ($rows as $row) { $data .= ',' . $row[$key_field]; } $data = substr($data, 1); } - - $this->Conn->Destroy($rs); } if ( !is_array($data) ) { @@ -1205,18 +1203,16 @@ $sql = str_replace('COUNT(*) AS count', $list->TableName . '.' . $list->IDField, $sql); $ids = ''; - $rs = $this->Conn->QueryRaw($sql); + $rows = $this->Conn->GetIterator($sql); - if ( $this->Conn->RowCount($rs) ) { - while ( ($row = $this->Conn->GetNextRow($rs)) ) { + if ( count($rows) ) { + foreach ($rows as $row) { $ids .= ',' . $row[$list->IDField]; } $ids = substr($ids, 1); } - $this->Conn->Destroy($rs); - return $ids; } } \ No newline at end of file Index: branches/5.2.x/core/kernel/db/db_load_balancer.php =================================================================== diff -u -N -r15137 -r15239 --- branches/5.2.x/core/kernel/db/db_load_balancer.php (.../db_load_balancer.php) (revision 15137) +++ branches/5.2.x/core/kernel/db/db_load_balancer.php (.../db_load_balancer.php) (revision 15239) @@ -1,6 +1,6 @@ Application->makeClass( 'kDBConnection', Array ($this->dbType, $this->errorHandler, $server['serverIndex']) ); + $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 = $this->Application->isDebugMode(); + $db->debugMode = $debug_mode; $db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], true, !$is_master); return $db; Index: branches/5.2.x/core/kernel/application.php =================================================================== diff -u -N -r15233 -r15239 --- branches/5.2.x/core/kernel/application.php (.../application.php) (revision 15233) +++ branches/5.2.x/core/kernel/application.php (.../application.php) (revision 15239) @@ -1,6 +1,6 @@ registerDefaultClasses(); $vars = kUtil::parseConfig(true); - $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : 'kDBConnection'; + $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection'); $this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array (&$this, 'handleSQLError'))); $this->Conn->setup($vars); @@ -780,6 +780,7 @@ // database $this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php'); + $this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php'); $this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php'); $this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php'); Index: branches/5.2.x/core/kernel/db/db_connection.php =================================================================== diff -u -N -r14934 -r15239 --- branches/5.2.x/core/kernel/db/db_connection.php (.../db_connection.php) (revision 14934) +++ branches/5.2.x/core/kernel/db/db_connection.php (.../db_connection.php) (revision 15239) @@ -1,6 +1,6 @@ _queryCount++; $this->lastQuery = $sql; - if ( !$no_debug ) { - $this->_queryCount++; - } - if ( $this->debugMode && !$no_debug ) { - return $this->debugQuery($sql, $key_field); - } $query_func = $this->getMetaFunction('query'); // set 1st checkpoint: begin - if ( $this->_captureStatistics ) { - $start_time = microtime(true); - } + $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)) ) { + while (($row = $fetch_func($this->queryID))) { $ret[$row[$key_field]] = $row; } } else { - while ( ($row = $fetch_func($this->queryID)) ) { + 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 && !$no_debug ) { + + 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 { @@ -640,95 +638,71 @@ } /** - * Retrieves data from database and return resource id on success or false otherwise + * 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 $select_sql - * @return resource + * @param string $sql + * @param string $key_field + * @param bool $no_debug + * @param string $iterator_class + * @return kMySQLQuery|bool * @access public - * @see kDBConnection::GetNextRow */ - public function QueryRaw($select_sql) + public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery') { - $this->lastQuery = $select_sql; $this->_queryCount++; + $this->lastQuery = $sql; $query_func = $this->getMetaFunction('query'); // set 1st checkpoint: begin - if ($this->_captureStatistics) { - $start_time = microtime(true); - } + $start_time = $this->_captureStatistics ? microtime(true) : 0; // set 1st checkpoint: end $this->setError(0, ''); // reset error - $resource = $query_func($select_sql, $this->connectionID); + $this->queryID = $query_func($sql, $this->connectionID); - // set 2nd checkpoint: begin - if ($this->_captureStatistics) { - $query_time = microtime(true) - $start_time; - if ($query_time > DBG_MAX_SQL_TIME) { - $this->Application->logSlowQuery($select_sql, $query_time); - } - $this->_queryTime += $query_time; - } - // set 2nd checkpoint: end + if ( is_resource($this->queryID) ) { + $ret = new $iterator_class($this->queryID, $key_field); + /* @var $ret kMySQLQuery */ - if ( is_resource($resource) ) { - return $resource; - } + // set 2nd checkpoint: begin + if ( $this->_captureStatistics ) { + $query_time = microtime(true) - $start_time; - return $this->showError($select_sql); - } + if ( $query_time > DBG_MAX_SQL_TIME ) { + $this->Application->logSlowQuery($sql, $query_time); + } - /** - * Returns row count in recordset - * - * @param resource $resource - * @return int - * @access public - */ - public function RowCount($resource) - { - $count_func = $this->getMetaFunction('num_rows'); + $this->_queryTime += $query_time; + } + // set 2nd checkpoint: end - return $count_func($resource); - } + return $ret; + } + else { + // set 2nd checkpoint: begin + if ( $this->_captureStatistics ) { + $this->_queryTime += microtime(true) - $start_time; + } + // set 2nd checkpoint: end + } - /** - * Returns next available row from recordset - * - * @param resource $resource - * @param string $fetch_type - * @return Array - * @access public - * @see kDBConnection::QueryRaw - */ - public function GetNextRow($resource, $fetch_type = 'assoc') - { - $fetch_func = $this->getMetaFunction('fetch_' . $fetch_type); - - return $fetch_func($resource); + return $this->showError($sql, $key_field, $no_debug); } /** * Free memory used to hold recordset handle * - * @param resource $resource * @access public - * @see kDBConnection::QueryRaw */ - public function Destroy($resource = null) + public function Destroy() { - if ( !isset($resource) ) { - $resource = $this->queryID; - } + $free_func = $this->getMetaFunction('free_result'); + $free_func($this->queryID); - if ( $resource ) { - $free_func = $this->getMetaFunction('free_result'); - $free_func($resource); - - $this->queryID = null; - } + unset($this->queryID); } /** @@ -741,83 +715,12 @@ public function ChangeQuery($sql) { $this->Query($sql); - return $this->errorCode == 0 ? true : false; + + return !$this->hasError(); } - /** - * 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. - * - * Each database query will be profiled into Debugger. - * - * @param string $sql - * @param string $key_field - * @return Array - * @access public - */ - protected function debugQuery($sql, $key_field = null) - { - global $debugger; - $query_func = $this->getMetaFunction('query'); - // set 1st checkpoint: begin - $profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE; - if ( $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 ( $profileSQLs ) { - $first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : 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->serverIndex); - $debugger->profilerAddTotal('sql', 'sql_' . $queryID); - $this->nextQueryCachable = false; - } - // set 2nd checkpoint: end - - $this->Destroy(); - return $ret; - } - else { - // set 2nd checkpoint: begin - if ( $profileSQLs ) { - $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverIndex); - $debugger->profilerAddTotal('sql', 'sql_' . $queryID); - $this->nextQueryCachable = false; - } - // set 2nd checkpoint: end - } - - return $this->showError($sql, $key_field); - } - /** * Returns auto increment field value from * insert like operation if any, zero otherwise @@ -1079,7 +982,7 @@ public function getSlaveLag() { // don't use kDBConnection::Query method, since it will create an array of all server processes - $rs = $this->QueryRaw('SHOW PROCESSLIST'); + $processes = $this->GetIterator('SHOW PROCESSLIST'); $skip_states = Array ( 'Waiting for master to send event', @@ -1090,17 +993,471 @@ ); // find slave SQL thread - while ( $row = $this->GetNextRow($rs, 'array') ) { - if ( $row['User'] == 'system user' && !in_array($row['State'], $skip_states) ) { + foreach ($processes as $process) { + if ( $process['User'] == 'system user' && !in_array($process['State'], $skip_states) ) { // this is it, return the time (except -ve) - $this->Destroy($rs); - return $row['Time'] > 0x7fffffff ? false : $row['Time']; + return $process['Time'] > 0x7fffffff ? false : $process['Time']; } } - $this->Destroy($rs); - return false; } - } \ No newline at end of file + } + + +class kDBConnectionDebug extends kDBConnection { + + protected $_profileSQLs = 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) + { + parent::__construct($dbType, $errorHandler, $server_index); + + $this->_profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE; + } + + /** + * 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 ) { + $first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : 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->serverIndex); + $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->serverIndex); + $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. + * + * @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; + + $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->serverIndex); + $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->serverIndex); + $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_array($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); + } +}