'', 'user' => '', 'pass' => '', 'db' => ''); /** * Handle of currenty processed recordset * * @var resource * @access private */ var $queryID = null; /** * DB type specific function mappings * * @var Array * @access private */ var $metaFunctions = Array(); /** * Function to handle sql errors * * @var string * @access private */ var $errorHandler = ''; /** * Error code * * @var int * @access private */ var $errorCode = 0; /** * Error message * * @var string * @access private */ var $errorMessage = ''; /** * Defines if database connection * operations should generate debug * information * * @var bool */ var $debugMode = false; /** * Save query execution statistics * * @var bool */ var $_captureStatistics = false; /** * Last query to database * * @var string */ var $lastQuery = ''; /** * Total processed queries count * * @var int */ var $_queryCount = 0; /** * Total time, used for serving queries * * @var Array */ var $_queryTime = 0; /** * Indicates, that next database query could be cached, when memory caching is enabled * * @var bool */ var $nextQueryCachable = false; /** * Initializes connection class with * db type to used in future * * @param string $dbType * @return DBConnection * @access public */ public function __construct($dbType, $errorHandler = '') { if ( class_exists('kApplication') ) { // prevents "Fatal Error" on 2nd installation step (when database is empty) parent::__construct(); } $this->dbType = $dbType; // $this->initMetaFunctions(); if (!$errorHandler) { $this->errorHandler = Array(&$this, 'handleError'); } else { $this->errorHandler = $errorHandler; } $this->_captureStatistics = defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !(defined('ADMIN') && ADMIN); } /** * Set's custom error * * @param int $code * @param string $msg * @access public */ function setError($code, $msg) { $this->errorCode = $code; $this->errorMessage = $msg; } /** * Checks if previous query execution * raised an error. * * @return bool * @access public */ function hasError() { return $this->errorCode != 0; } /** * Caches function specific to requested * db type * * @access private */ function initMetaFunctions() { $ret = Array(); switch ($this->dbType) { case 'mysql': $ret = Array(); // only define functions, that name differs from "dbType_" break; } $this->metaFunctions = $ret; } /** * Get's function for specific db type * based on it's meta name * * @param string $name * @return string * @access private */ function getMetaFunction($name) { /*if (!isset($this->metaFunctions[$name])) { $this->metaFunctions[$name] = $name; }*/ return $this->dbType.'_'.$name; } /** * Try to connect to database server * using specified parameters and set * database to $db if connection made * * @param string $host * @param string $user * @param string $pass * @param string $db * @access public */ function Connect($host, $user, $pass, $db, $force_new = false, $retry = false) { $this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db); $func = $this->getMetaFunction('connect'); $this->connectionID = $func($host, $user, $pass, $force_new); if ($this->connectionID) { if (defined('DBG_SQL_MODE')) { $this->Query('SET sql_mode = \''.DBG_SQL_MODE.'\''); } if (defined('SQL_COLLATION') && defined('SQL_CHARSET')) { $this->Query('SET NAMES \''.SQL_CHARSET.'\' COLLATE \''.SQL_COLLATION.'\''); } $this->setError(0, ''); // reset error $this->setDB($db); } // process error (fatal in most cases) $func = $this->getMetaFunction('errno'); $this->errorCode = $this->connectionID ? $func($this->connectionID) : $func(); if ( !$this->hasError() ) { return true; } $func = $this->getMetaFunction('error'); $this->errorMessage = $this->connectionID ? $func($this->connectionID) : $func(); $error_msg = 'Database connection failed, please check your connection settings.
Error (' . $this->errorCode . '): ' . $this->errorMessage; if ( (defined('IS_INSTALL') && IS_INSTALL) || $retry ) { trigger_error($error_msg, E_USER_WARNING); } else { throw new Exception($error_msg); } return false; } function ReConnect($force_new = false) { $retry_count = 0; $func = $this->getMetaFunction('close'); $func($this->connectionID); while ($retry_count < 3) { sleep(5); // wait 5 seconds before each reconnect attempt $connected = $this->Connect( $this->connectionParams['host'], $this->connectionParams['user'], $this->connectionParams['pass'], $this->connectionParams['db'], $force_new, true ); if ($connected) { break; } $retry_count++; } return $connected; } /** * Shows error message from previous operation * if it failed * * @access private */ function showError($sql = '', $key_field = null, $no_debug = false) { static $retry_count = 0; $func = $this->getMetaFunction('errno'); if (!$this->connectionID) { // no connection while doing mysql_query $this->errorCode = $func(); if ( $this->hasError() ) { $func = $this->getMetaFunction('error'); $this->errorMessage = $func(); $ret = $this->callErrorHandler($sql); if (!$ret) { exit; } } return false; } // checking if there was an error during last mysql_query $this->errorCode = $func($this->connectionID); if ( $this->hasError() ) { $func = $this->getMetaFunction('error'); $this->errorMessage = $func($this->connectionID); $ret = $this->callErrorHandler($sql); if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && ($retry_count < 3) ) { // #2006 - MySQL server has gone away // #2013 - Lost connection to MySQL server during query $retry_count++; if ( $this->ReConnect() ) { return $this->Query($sql, $key_field, $no_debug); } } if (!$ret) { exit; } } else { $retry_count = 0; } return false; } function callErrorHandler($sql) { if (is_array($this->errorHandler)) { $func = $this->errorHandler[1]; $ret = $this->errorHandler[0]->$func($this->errorCode, $this->errorMessage, $sql); } else { $func = $this->errorHandler; $ret = $func($this->errorCode, $this->errorMessage, $sql); } return $ret; } /** * Default error handler for sql errors * * @param int $code * @param string $msg * @param string $sql * @return bool * @access private */ function handleError($code, $msg, $sql) { echo 'Processing SQL: ' . $sql . '
'; echo 'Error (' . $code . '): ' . $msg . '
'; return false; } /** * Set's database name for connection * to $new_name * * @param string $new_name * @return bool * @access public */ function setDB($new_name) { if (!$this->connectionID) return false; $func = $this->getMetaFunction('select_db'); return $func($new_name, $this->connectionID); } /** * Returns first field of first line * of recordset if query ok or false * otherwise * * @param string $sql * @param int $offset * @return string * @access 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 */ 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 */ 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; } /** * 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 * @return Array */ function Query($sql, $key_field = null, $no_debug = false) { $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); } // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $query_func($sql,$this->connectionID); if (is_resource($this->queryID)) { $ret = Array(); $fetch_func = $this->getMetaFunction('fetch_assoc'); if (isset($key_field)) { while (($row = $fetch_func($this->queryID))) { $ret[$row[$key_field]] = $row; } } else { while (($row = $fetch_func($this->queryID))) { $ret[] = $row; } } // set 2nd checkpoint: begin if ($this->_captureStatistics) { $query_time = microtime(true) - $start_time; if ($query_time > DBG_MAX_SQL_TIME && !$no_debug) { $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); } function ChangeQuery($sql) { $this->Query($sql); return $this->errorCode == 0 ? true : false; } 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); $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); $debugger->profilerAddTotal('sql', 'sql_'.$queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field); } /** * Free memory used to hold recordset handle * * @access private */ function Destroy() { if($this->queryID) { $free_func = $this->getMetaFunction('free_result'); $free_func($this->queryID); $this->queryID = null; } } /** * Returns auto increment field value from * insert like operation if any, zero otherwise * * @return int * @access public */ function getInsertID() { $func = $this->getMetaFunction('insert_id'); return $func($this->connectionID); } /** * Returns row count affected by last query * * @return int * @access public */ function getAffectedRows() { $func = $this->getMetaFunction('affected_rows'); return $func($this->connectionID); } /** * Returns LIMIT sql clause part for specific db * * @param int $offset * @param int $rows * @return string * @access private */ function getLimitClause($offset, $rows) { if(!($rows > 0)) return ''; switch ($this->dbType) { default: return 'LIMIT '.$offset.','.$rows; break; } } /** * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0) * Otherwise returns as-is * * @param mixed $string * * @return string */ function qstr($string) { if ( is_null($string) ) { return 'NULL'; } # This will also quote numeric values. This should be harmless, # and protects against weird problems that occur when they really # _are_ strings such as article titles and string->number->string # conversion is not 1:1. return "'" . mysql_real_escape_string($string, $this->connectionID) . "'"; } /** * Escapes strings (only work since PHP 4.3.0) * * @param mixed $string * * @return string */ function escape($string) { if ( is_null($string) ) { return 'NULL'; } $string = mysql_real_escape_string($string, $this->connectionID); // prevent double-escaping of MySQL wildcard symbols ("%" and "_") in case if they were already escaped return str_replace(Array ('\\\\%', '\\\\_'), Array ('\\%', '\\_'), $string); } /** * Returns last error code occured * * @return int */ function getErrorCode() { return $this->errorCode; } /** * Returns last error message * * @return string * @access 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 */ 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; } 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 presense in database * * @param string $table_name * @return bool */ function TableFound($table_name) { static $table_found = Array(); if (!preg_match('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', $table_name)) { $table_name = TABLE_PREFIX.$table_name; } if (!isset($table_found[$table_name])) { $table_found[$table_name] = $this->Query('SHOW TABLES LIKE "'.$table_name.'"'); } return $table_found[$table_name]; } /** * Returns query processing statistics * * @return Array */ function getQueryStatistics() { return Array ('time' => $this->_queryTime, 'count' => $this->_queryCount); } }