Index: branches/5.2.x/core/kernel/utility/debugger.php =================================================================== diff -u -N -r14585 -r14609 --- branches/5.2.x/core/kernel/utility/debugger.php (.../debugger.php) (revision 14585) +++ branches/5.2.x/core/kernel/utility/debugger.php (.../debugger.php) (revision 14609) @@ -1,6 +1,6 @@ rowSeparator = '@'.(is_object($application->Factory) ? $application->GetSID() : 0).'@'; + $this->rowSeparator = '@' . (is_object($application->Factory) ? $application->GetSID() : 0) . '@'; // $this->rowSeparator = '@'.rand(0,100000).'@'; // include debugger files from this url - $reg_exp = '/^'.preg_quote(FULL_PATH, '/').'/'; + $reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/'; $kernel_path = preg_replace($reg_exp, '', KERNEL_PATH, 1); - $this->baseURL = PROTOCOL.SERVER_NAME.(defined('PORT') ? ':'.PORT : '').rtrim(BASE_PATH, '/').$kernel_path.'/utility/debugger'; + $this->baseURL = PROTOCOL . SERVER_NAME . (defined('PORT') ? ':' . PORT : '') . rtrim(BASE_PATH, '/') . $kernel_path . '/utility/debugger'; // save debug output in this folder $this->tempFolder = defined('RESTRICTED') ? RESTRICTED : WRITEABLE . '/cache'; @@ -965,8 +967,13 @@ $trace_count = count($trace_results); $i = 0; while ($i < $trace_count) { + if ( !isset($trace_results[$i]['file']) ) { + $i++; + continue; + } + $trace_file = basename($trace_results[$i]['file']); - if ($trace_file != 'db_connection.php' && $trace_file != 'adodb.inc.php') { + if ($trace_file != 'db_connection.php' && $trace_file != 'db_load_balancer.php' && $trace_file != 'adodb.inc.php') { break; } $i++; @@ -977,6 +984,8 @@ if (array_key_exists('object', $trace_results[$i + 1]) && isset($trace_results[$i + 1]['object']->Prefix)) { $object =& $trace_results[$i + 1]['object']; + /* @var $object kBase */ + $prefix_special = rtrim($object->Prefix . '.' . $object->Special, '.'); $this->ProfilerData[$key]['prefix_special'] = $prefix_special; } @@ -1023,6 +1032,10 @@ $this->ProfilerData[$key]['subtitle'] = 'cachable'; } + if ($func_arguments[7]) { + $additional[] = Array ('name' => 'Server #', 'value' => $func_arguments[7]); + } + if (array_key_exists('prefix_special', $this->ProfilerData[$key])) { $additional[] = Array ('name' => 'PrefixSpecial', 'value' => $this->ProfilerData[$key]['prefix_special']); } @@ -1154,8 +1167,13 @@ /** * Generates report * + * @param bool $returnResult + * @param bool $clean_output_buffer + * + * @return string + * @access public */ - function printReport($returnResult = false, $clean_output_buffer = true) + public function printReport($returnResult = false, $clean_output_buffer = true) { if ($this->reportDone) { // don't print same report twice (in case if shutdown function used + compression + fatal error) @@ -1192,7 +1210,7 @@ $this->appendHTML($this->highlightString($this->print_r($this->ProfilePoints, true))); /*foreach ($this->ProfilePoints as $point => $locations) { - foreach ($locations as $location => $occurences) { + foreach ($locations as $location => $occurrences) { } @@ -1338,6 +1356,8 @@ $this->reportDone = true; } + + return ''; } /** @@ -1402,13 +1422,16 @@ /** * User-defined error handler * + * @throws Exception * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline * @param array $errcontext + * @return bool + * @access public */ - function saveError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '') + public function saveError($errno, $errstr, $errfile = null, $errline = null, $errcontext = Array ()) { $this->ProfilerData['error_handling']['begins'] = memory_get_usage(); @@ -1425,7 +1448,7 @@ } if ( DebuggerUtil::constOn('DBG_IGNORE_STRICT_ERRORS') && defined('E_STRICT') && ($errno == E_STRICT) ) { - return ; + return false; } $this->expandError($errstr, $errfile, $errline); @@ -1446,14 +1469,18 @@ // append debugger report to data in buffer & clean buffer afterwards die( $this->breakOutofBuffering(false) . $this->printReport(true) ); } + + return true; } /** * User-defined exception handler * - * @param Exception $errno + * @param Exception $exception + * @return void + * @access public */ - function saveException($exception) + public function saveException($exception) { $this->ProfilerData['error_handling']['begins'] = memory_get_usage(); @@ -1534,9 +1561,16 @@ $this->appendHTML($ret); } - function AttachToApplication() { - if (!DebuggerUtil::constOn('DBG_HANDLE_ERRORS')) { - return true; + /** + * Attaches debugger to Application + * + * @return void + * @access public + */ + public function AttachToApplication() + { + if ( !DebuggerUtil::constOn('DBG_HANDLE_ERRORS') ) { + return; } if ( class_exists('kApplication') ) { @@ -1551,8 +1585,8 @@ $this->Application->exceptionHandlers[] = Array (&$this, 'saveException'); } else { - set_error_handler( Array(&$this, 'saveError') ); - set_exception_handler( Array(&$this, 'saveException') ); + set_error_handler( Array (&$this, 'saveError') ); + set_exception_handler( Array (&$this, 'saveException') ); } } Index: branches/5.2.x/core/kernel/startup.php =================================================================== diff -u -N -r14588 -r14609 --- branches/5.2.x/core/kernel/startup.php (.../startup.php) (revision 14588) +++ branches/5.2.x/core/kernel/startup.php (.../startup.php) (revision 14609) @@ -1,6 +1,6 @@ dbType = $dbType; + $this->errorHandler = $errorHandler; + + $this->DBClusterTimeout *= 1e6; // convert to milliseconds + } + + /** + * 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); + + 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; + } + + /** + * Really opens a connection. + * Returns a database object whether or not the connection was successful. + * + * @param Array $server + * @return kDBConnection + */ + protected function &reallyOpenConnection($server) + { + $db =& $this->Application->makeClass( 'kDBConnection', Array ($this->dbType, $this->errorHandler, $server['serverIndex']) ); + /* @var $db kDBConnection */ + + $db->debugMode = $this->Application->isDebugMode(); + $db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], true, true); + + return $db; + } + + /** + * 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 + * + * @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 + * + * @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); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0) + * Otherwise returns as-is + * + * @param mixed $string + * @return string + * @access public + */ + public function qstr($string) + { + $conn =& $this->openConnection($this->lastUsedIndex); + + return $conn->qstr($string); + } + + /** + * 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) + { + $conn =& $this->openConnection( $this->getMasterIndex() ); + + return $conn->doInsert($fields_hash, $table, $type, $insert_now); + } + + /** + * 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); + } + + /** + * 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; + } +} Index: branches/5.2.x/core/install.php =================================================================== diff -u -N -r14585 -r14609 --- branches/5.2.x/core/install.php (.../install.php) (revision 14585) +++ branches/5.2.x/core/install.php (.../install.php) (revision 14609) @@ -1,6 +1,6 @@ stepsPreset = $preset; } - function GetVar($name) + /** + * Returns variable from request + * + * @param string $name + * @return string|bool + * @access private + */ + private function GetVar($name) { return array_key_exists($name, $_REQUEST) ? $_REQUEST[$name] : false; } - function SetVar($name, $value) + /** + * Sets new value for request variable + * + * @param string $name + * @param mixed $value + * @return void + * @access private + */ + private function SetVar($name, $value) { $_REQUEST[$name] = $value; } @@ -1177,6 +1192,8 @@ } $upgrade_object = new $upgrade_classes[$module_path](); + /* @var $upgrade_object CoreUpgrades */ + $upgrade_object->setToolkit($this->toolkit); return $upgrade_object; @@ -1254,17 +1271,12 @@ } $this->Conn = new kDBConnection($this->toolkit->getSystemConfig('Database', 'DBType'), Array(&$this, 'DBErrorHandler')); - $this->Conn->Connect( - $this->toolkit->getSystemConfig('Database', 'DBHost'), - $this->toolkit->getSystemConfig('Database', 'DBUser'), - $this->toolkit->getSystemConfig('Database', 'DBUserPassword'), - $this->toolkit->getSystemConfig('Database', 'DBName') - ); + $this->Conn->setup( $this->toolkit->systemConfig ); // setup toolkit too $this->toolkit->Conn =& $this->Conn; - return $this->Conn->errorCode == 0; + return !$this->Conn->hasError(); } /** Index: branches/5.2.x/core/kernel/utility/cache.php =================================================================== diff -u -N -r14585 -r14609 --- branches/5.2.x/core/kernel/utility/cache.php (.../cache.php) (revision 14585) +++ branches/5.2.x/core/kernel/utility/cache.php (.../cache.php) (revision 14609) @@ -1,6 +1,6 @@ Application->Conn->nextQueryFromMaster = true; $handler_class = $this->Application->ConfigValue('CacheHandler') . 'CacheHandler'; } Index: branches/5.2.x/core/kernel/application.php =================================================================== diff -u -N -r14599 -r14609 --- branches/5.2.x/core/kernel/application.php (.../application.php) (revision 14599) +++ branches/5.2.x/core/kernel/application.php (.../application.php) (revision 14609) @@ -1,6 +1,6 @@ Factory = new kFactory(); $this->registerDefaultClasses(); - $this->Conn =& $this->Factory->makeClass('kDBConnection', Array (SQL_TYPE, Array (&$this, 'handleSQLError'))); - $this->Conn->debugMode = $this->isDebugMode(); - $this->Conn->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB); + $vars = kUtil::parseConfig(true); + $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : 'kDBConnection'; + $this->Conn =& $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array (&$this, 'handleSQLError'))); + $this->Conn->setup($vars); $this->cacheManager =& $this->makeClass('kCacheManager'); $this->cacheManager->InitCache(); @@ -391,7 +392,7 @@ $language =& $this->recallObject('lang.current', null, Array ('live_table' => true)); /* @var $language LanguagesItem */ - + if ( preg_match('/utf-8/', $language->GetDBField('Charset')) ) { setlocale(LC_ALL, 'en_US.UTF-8'); mb_internal_encoding('UTF-8'); @@ -729,6 +730,7 @@ // database $this->registerClass('kDBConnection', 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', null, 'kDBItem'); $this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php'); @@ -1207,7 +1209,7 @@ { $session =& $this->recallObject('Session'); /* @var $session Session */ - + $session->Destroy(); } @@ -1682,7 +1684,7 @@ { return $this->cacheManager->SetConfigValue($name, $value); } - + /** * Allows to process any type of event * Index: branches/5.2.x/core/kernel/globals.php =================================================================== diff -u -N -r14585 -r14609 --- branches/5.2.x/core/kernel/globals.php (.../globals.php) (revision 14585) +++ branches/5.2.x/core/kernel/globals.php (.../globals.php) (revision 14609) @@ -1,6 +1,6 @@ isDebugMode(); } - if ($is_debug) { - if ($label) { + if ( $is_debug && isset($application) ) { + if ( $label ) { $application->Debugger->appendHTML('' . $label . ''); } $application->Debugger->dumpVars($data); } else { - if ($label) { + if ( $label ) { echo '' . $label . '
'; } @@ -155,6 +156,10 @@ require($file); if ($parse_section) { + if ( isset($_CONFIG['Database']['LoadBalancing']) && $_CONFIG['Database']['LoadBalancing'] ) { + require FULL_PATH . DIRECTORY_SEPARATOR . 'system' . DIRECTORY_SEPARATOR . 'db_servers.php'; + } + return $_CONFIG; } @@ -316,6 +321,9 @@ * * @param string $url * @param mixed $data + * @param Array $headers + * @param string $request_type + * @param Array $curl_options * @return string * @access public * @deprecated Index: branches/5.2.x/core/kernel/db/db_connection.php =================================================================== diff -u -N -r14585 -r14609 --- branches/5.2.x/core/kernel/db/db_connection.php (.../db_connection.php) (revision 14585) +++ branches/5.2.x/core/kernel/db/db_connection.php (.../db_connection.php) (revision 14609) @@ -1,6 +1,6 @@ '', 'user' => '', 'pass' => '', 'db' => ''); + protected $connectionParams = Array ('host' => '', 'user' => '', 'pass' => '', 'db' => ''); /** - * Handle of currenty processed recordset + * Index of database server * + * @var int + * @access protected + */ + protected $serverIndex = 0; + + /** + * Handle of currently processed recordset + * * @var resource - * @access private + * @access protected */ - var $queryID = null; + protected $queryID = null; /** * DB type specific function mappings * * @var Array - * @access private + * @access protected */ - var $metaFunctions = Array(); + protected $metaFunctions = Array (); /** * Function to handle sql errors * - * @var string - * @access private + * @var Array|string + * @access public */ - var $errorHandler = ''; + public $errorHandler = ''; /** * Error code * * @var int - * @access private + * @access protected */ - var $errorCode = 0; + protected $errorCode = 0; /** * Error message * * @var string - * @access private + * @access protected */ - var $errorMessage = ''; + protected $errorMessage = ''; /** * Defines if database connection * operations should generate debug * information * * @var bool + * @access public */ - var $debugMode = false; + public $debugMode = false; /** * Save query execution statistics * * @var bool + * @access protected */ - var $_captureStatistics = false; + protected $_captureStatistics = false; /** * Last query to database * * @var string + * @access public */ - var $lastQuery = ''; + public $lastQuery = ''; /** * Total processed queries count * * @var int + * @access protected */ - var $_queryCount = 0; + protected $_queryCount = 0; /** * Total time, used for serving queries * * @var Array + * @access protected */ - var $_queryTime = 0; + protected $_queryTime = 0; /** * Indicates, that next database query could be cached, when memory caching is enabled * * @var bool + * @access public */ - var $nextQueryCachable = false; + 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 - * @return DBConnection + * @param string $errorHandler + * @param int $server_index * @access public */ - public function __construct($dbType, $errorHandler = '') + 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'); @@ -159,9 +193,9 @@ * * @param int $code * @param string $msg - * @access public + * @access protected */ - function setError($code, $msg) + protected function setError($code, $msg) { $this->errorCode = $code; $this->errorMessage = $msg; @@ -174,7 +208,7 @@ * @return bool * @access public */ - function hasError() + public function hasError() { return $this->errorCode != 0; } @@ -183,38 +217,37 @@ * Caches function specific to requested * db type * - * @access private + * @access protected */ - function initMetaFunctions() + protected function initMetaFunctions() { - $ret = Array(); - switch ($this->dbType) - { + $ret = Array (); + + switch ( $this->dbType ) { case 'mysql': - $ret = Array(); // only define functions, that name differs from "dbType_" + $ret = Array (); // only define functions, that name differs from "dbType_" break; - - } + $this->metaFunctions = $ret; } /** - * Get's function for specific db type + * Gets function for specific db type * based on it's meta name * * @param string $name * @return string - * @access private + * @access protected */ - function getMetaFunction($name) + protected function getMetaFunction($name) { - /*if (!isset($this->metaFunctions[$name])) { + /*if ( !isset($this->metaFunctions[$name]) ) { $this->metaFunctions[$name] = $name; }*/ - return $this->dbType.'_'.$name; + return $this->dbType . '_' . $name; } @@ -227,9 +260,12 @@ * @param string $user * @param string $pass * @param string $db + * @param bool $force_new + * @param bool $retry + * @return bool * @access public */ - function Connect($host, $user, $pass, $db, $force_new = false, $retry = false) + public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false) { $this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db); @@ -253,7 +289,9 @@ $func = $this->getMetaFunction('errno'); $this->errorCode = $this->connectionID ? $func($this->connectionID) : $func(); - if ( !$this->hasError() ) { + if ( is_resource($this->connectionID) && !$this->hasError() ) { + $this->connectionOpened = true; + return true; } @@ -268,17 +306,48 @@ throw new Exception($error_msg); } + $this->connectionOpened = false; + return false; } - function ReConnect($force_new = false) + /** + * 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) { + while ( $retry_count < 3 ) { sleep(5); // wait 5 seconds before each reconnect attempt $connected = $this->Connect( @@ -289,7 +358,7 @@ $force_new, true ); - if ($connected) { + if ( $connected ) { break; } @@ -303,12 +372,16 @@ * Shows error message from previous operation * if it failed * - * @access private + * @param string $sql + * @param string $key_field + * @param bool $no_debug + * @return bool + * @access protected */ - function showError($sql = '', $key_field = null, $no_debug = false) + protected function showError($sql = '', $key_field = null, $no_debug = false) { static $retry_count = 0; - + $func = $this->getMetaFunction('errno'); if (!$this->connectionID) { @@ -359,7 +432,14 @@ return false; } - function callErrorHandler($sql) + /** + * 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]; @@ -380,9 +460,9 @@ * @param string $msg * @param string $sql * @return bool - * @access private + * @access public */ - function handleError($code, $msg, $sql) + public function handleError($code, $msg, $sql) { echo 'Processing SQL: ' . $sql . '
'; echo 'Error (' . $code . '): ' . $msg . '
'; @@ -396,9 +476,9 @@ * * @param string $new_name * @return bool - * @access public + * @access protected */ - function setDB($new_name) + protected function setDB($new_name) { if (!$this->connectionID) return false; $func = $this->getMetaFunction('select_db'); @@ -415,11 +495,14 @@ * @return string * @access public */ - function GetOne($sql, $offset = 0) + public function GetOne($sql, $offset = 0) { $row = $this->GetRow($sql, $offset); - if(!$row) return false; + if ( !$row ) { + return false; + } + return array_shift($row); } @@ -432,12 +515,15 @@ * @return Array * @access public */ - function GetRow($sql, $offset = 0) + public function GetRow($sql, $offset = 0) { - $sql .= ' '.$this->getLimitClause($offset, 1); + $sql .= ' ' . $this->getLimitClause($offset, 1); $ret = $this->Query($sql); - if(!$ret) return false; + if ( !$ret ) { + return false; + } + return array_shift($ret); } @@ -453,25 +539,30 @@ * @return Array * @access public */ - function GetCol($sql, $key_field = null) + public function GetCol($sql, $key_field = null) { $rows = $this->Query($sql); - if (!$rows) return $rows; + if ( !$rows ) { + return $rows; + } - $i = 0; $row_count = count($rows); - $ret = Array(); - if (isset($key_field)) { - while ($i < $row_count) { + $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) { + while ( $i < $row_count ) { $ret[] = array_shift($rows[$i]); $i++; } } + return $ret; } @@ -484,46 +575,48 @@ * * @param string $sql * @param string $key_field + * @param bool $no_debug * @return Array + * @access public */ - function Query($sql, $key_field = null, $no_debug = false) + public function Query($sql, $key_field = null, $no_debug = false) { $this->lastQuery = $sql; - if (!$no_debug) { + if ( !$no_debug ) { $this->_queryCount++; } - if ($this->debugMode && !$no_debug) { - return $this->debugQuery($sql,$key_field); + if ( $this->debugMode && !$no_debug ) { + return $this->debugQuery($sql, $key_field); } $query_func = $this->getMetaFunction('query'); // set 1st checkpoint: begin - if ($this->_captureStatistics) { + if ( $this->_captureStatistics ) { $start_time = microtime(true); } // set 1st checkpoint: end $this->setError(0, ''); // reset error - $this->queryID = $query_func($sql,$this->connectionID); - if (is_resource($this->queryID)) { - $ret = Array(); + $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))) { + if ( isset($key_field) ) { + 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) { + 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 && !$no_debug ) { $this->Application->logSlowQuery($sql, $query_time); } $this->_queryTime += $query_time; @@ -535,7 +628,7 @@ } else { // set 2nd checkpoint: begin - if ($this->_captureStatistics) { + if ( $this->_captureStatistics ) { $this->_queryTime += microtime(true) - $start_time; } // set 2nd checkpoint: end @@ -544,57 +637,73 @@ return $this->showError($sql, $key_field, $no_debug); } - function ChangeQuery($sql) + /** + * Performs sql query, that will change database content + * + * @param string $sql + * @return bool + * @access public + */ + public function ChangeQuery($sql) { $this->Query($sql); return $this->errorCode == 0 ? true : false; } - function debugQuery($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. + * + * 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) { + if ( $profileSQLs ) { $queryID = $debugger->generateID(); - $debugger->profileStart('sql_'.$queryID, $debugger->formatSQL($sql)); + $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(); + if ( is_resource($this->queryID) ) { + $ret = Array (); $fetch_func = $this->getMetaFunction('fetch_assoc'); - if( isset($key_field) ) - { - while( ($row = $fetch_func($this->queryID)) ) - { + if ( isset($key_field) ) { + while ( ($row = $fetch_func($this->queryID)) ) { $ret[$row[$key_field]] = $row; } } - else - { - while( ($row = $fetch_func($this->queryID)) ) - { + else { + while ( ($row = $fetch_func($this->queryID)) ) { $ret[] = $row; } } // set 2nd checkpoint: begin - if ($profileSQLs) { + 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) . ' ...'; + 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); - $debugger->profilerAddTotal('sql', 'sql_'.$queryID); + $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 @@ -604,9 +713,9 @@ } else { // set 2nd checkpoint: begin - if ($profileSQLs) { - $debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable); - $debugger->profilerAddTotal('sql', 'sql_'.$queryID); + 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 @@ -618,12 +727,11 @@ /** * Free memory used to hold recordset handle * - * @access private + * @access public */ - function Destroy() + public function Destroy() { - if($this->queryID) - { + if ( $this->queryID ) { $free_func = $this->getMetaFunction('free_result'); $free_func($this->queryID); $this->queryID = null; @@ -637,7 +745,7 @@ * @return int * @access public */ - function getInsertID() + public function getInsertID() { $func = $this->getMetaFunction('insert_id'); return $func($this->connectionID); @@ -649,7 +757,7 @@ * @return int * @access public */ - function getAffectedRows() + public function getAffectedRows() { $func = $this->getMetaFunction('affected_rows'); return $func($this->connectionID); @@ -661,16 +769,17 @@ * @param int $offset * @param int $rows * @return string - * @access private + * @access public */ - function getLimitClause($offset, $rows) + public function getLimitClause($offset, $rows) { - if(!($rows > 0)) return ''; + if ( !($rows > 0) ) { + return ''; + } - switch ($this->dbType) { - + switch ( $this->dbType ) { default: - return 'LIMIT '.$offset.','.$rows; + return 'LIMIT ' . $offset . ',' . $rows; break; } } @@ -680,10 +789,10 @@ * Otherwise returns as-is * * @param mixed $string - * * @return string + * @access public */ - function qstr($string) + public function qstr($string) { if ( is_null($string) ) { return 'NULL'; @@ -700,10 +809,10 @@ * Escapes strings (only work since PHP 4.3.0) * * @param mixed $string - * * @return string + * @access public */ - function escape($string) + public function escape($string) { if ( is_null($string) ) { return 'NULL'; @@ -716,11 +825,12 @@ } /** - * Returns last error code occured + * Returns last error code occurred * * @return int + * @access public */ - function getErrorCode() + public function getErrorCode() { return $this->errorCode; } @@ -731,7 +841,7 @@ * @return string * @access public */ - function getErrorMsg() + public function getErrorMsg() { return $this->errorMessage; } @@ -745,8 +855,9 @@ * @param string $type * @param bool $insert_now * @return bool + * @access public */ - function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true) + public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true) { static $value_sqls = Array (); @@ -779,7 +890,16 @@ return $insert_result; } - function doUpdate($fields_hash, $table, $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; @@ -797,12 +917,13 @@ } /** - * Allows to detect table's presense in database + * Allows to detect table's presence in database * * @param string $table_name * @return bool + * @access public */ - function TableFound($table_name) + public function TableFound($table_name) { static $table_found = Array(); @@ -821,9 +942,59 @@ * Returns query processing statistics * * @return Array + * @access public */ - function getQueryStatistics() + 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 + $rs = mysql_query('SHOW PROCESSLIST', $this->connectionID); + + $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 + while ( $row = mysql_fetch_array($rs) ) { + if ( $row['User'] == 'system user' && !in_array($row['State'], $skip_states) ) { + // this is it, return the time (except -ve) + return $row['Time'] > 0x7fffffff ? false : $row['Time']; + } + } + + return false; + } } \ No newline at end of file Index: branches/5.2.x/core/units/admin/admin_events_handler.php =================================================================== diff -u -N -r14588 -r14609 --- branches/5.2.x/core/units/admin/admin_events_handler.php (.../admin_events_handler.php) (revision 14588) +++ branches/5.2.x/core/units/admin/admin_events_handler.php (.../admin_events_handler.php) (revision 14609) @@ -1,6 +1,6 @@ Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls'); } - function OnResetSections(&$event) + /** + * Resets tree section cache and refreshes admin section tree + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function OnResetSections(&$event) { if ($this->Application->GetVar('ajax') == 'yes') { $event->status = kEvent::erSTOP; @@ -585,14 +594,15 @@ function OnCSVImportStep(&$event) { $import_helper =& $this->Application->recallObject('CSVHelper'); - /* @var $export_helper kCSVHelper */ + /* @var $import_helper kCSVHelper */ $prefix_special = $import_helper->ImportData('prefix'); $prefix_elems = preg_split('/\.|_/', $prefix_special, 2); $perm_sections = $this->Application->getUnitOption($prefix_elems[0], 'PermSection'); - if(!$this->Application->CheckPermission($perm_sections['main'].'.add') && !$this->Application->CheckPermission($perm_sections['main'].'.edit')) { + + if ( !$this->Application->CheckPermission($perm_sections['main'] . '.add') && !$this->Application->CheckPermission($perm_sections['main'] . '.edit') ) { $event->status = kEvent::erPERM_FAIL; - return ; + return; } $import_helper->ImportStep(); @@ -1080,13 +1090,20 @@ } - function runSchemaText($sql) + /** + * Run given schema sqls and return error, if any + * + * @param $sql + * @return string + * @access protected + */ + protected function runSchemaText($sql) { - $table_prefix = 'restore'.TABLE_PREFIX; -// $table_prefix = TABLE_PREFIX; + $table_prefix = 'restore' . TABLE_PREFIX; - if (strlen($table_prefix) > 0) { + if ( strlen($table_prefix) > 0 ) { $replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO '); + foreach ($replacements as $replacement) { $sql = str_replace($replacement, $replacement . $table_prefix, $sql); } @@ -1095,69 +1112,68 @@ $sql = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sql); $sql = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sql); - $commands = explode("# --------------------------------------------------------",$sql); - if(count($commands)>0) - { -// $query_func = getConnectionInterface('query',$dbo_type); -// $errorno_func = getConnectionInterface('errorno',$dbo_type); -// $errormsg_func = getConnectionInterface('errormsg',$dbo_type); + $commands = explode("# --------------------------------------------------------", $sql); - for($i = 0; $i < count($commands); $i++) - { - $cmd = $commands[$i]; - $cmd = trim($cmd); - if(strlen($cmd)>0) - { - $this->Conn->Query($cmd); - if($this->Conn->errorCode != 0) - { - return $this->Conn->errorMessage." COMMAND:
$cmd
"; - } - } - } - } + if ( count($commands) > 0 ) { + for ($i = 0; $i < count($commands); $i++) { + $cmd = trim( $commands[$i] ); + + if ( strlen($cmd) > 0 ) { + $this->Conn->Query($cmd); + + if ( $this->Conn->hasError() ) { + return $this->Conn->getErrorMsg() . " COMMAND:
$cmd
"; + } + } + } + } + + return ''; } - function runSQLText($allsql) + /** + * Runs given sqls and return error message, if any + * + * @param $all_sqls + * @return string + * @access protected + */ + protected function runSQLText($all_sqls) { $line = 0; -// $query_func = getConnectionInterface('query',$dbo_type); -// $errorno_func = getConnectionInterface('errorno',$dbo_type); -// $errormsg_func = getConnectionInterface('errormsg',$dbo_type); - while($line0 && substr($sql,0,1)!="#") - { - $table_prefix = 'restore'.TABLE_PREFIX; + while ( $line < count($all_sqls) ) { + $sql = $all_sqls[$line]; + if ( strlen(trim($sql)) > 0 && substr($sql, 0, 1) != "#" ) { + $table_prefix = 'restore' . TABLE_PREFIX; - if (strlen($table_prefix) > 0) { + if ( strlen($table_prefix) > 0 ) { $replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO '); + foreach ($replacements as $replacement) { $sql = str_replace($replacement, $replacement . $table_prefix, $sql); } } $sql = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sql); $sql = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sql); - $sql = trim($sql); - if(strlen($sql)>0) - { + + if ( strlen($sql) > 0 ) { $this->Conn->Query($sql); - if($this->Conn->errorCode != 0) - { - return $this->Conn->errorMessage." COMMAND:
$sql
"; - } + if ( $this->Conn->hasError() ) { + return $this->Conn->getErrorMsg() . " COMMAND:
$sql
"; + } } } + $line++; } + + return ''; } - /** * Starts restore process *