Changeset View
Changeset View
Standalone View
Standalone View
core/kernel/db/db_connection.php
Show First 20 Lines • Show All 78 Lines • ▼ Show 20 Line(s) | |||||
* Error message | * Error message | ||||
* | * | ||||
* @var string | * @var string | ||||
* @access protected | * @access protected | ||||
*/ | */ | ||||
protected $errorMessage = ''; | protected $errorMessage = ''; | ||||
/** | /** | ||||
* Error retry count. | |||||
* | |||||
* @var integer | |||||
*/ | |||||
protected $errorRetryCount = 0; | |||||
/** | |||||
* Database query, that caused an error. | |||||
* | |||||
* @var string | |||||
*/ | |||||
protected $erroredQuery = ''; | |||||
/** | |||||
* Defines if database connection | * Defines if database connection | ||||
* operations should generate debug | * operations should generate debug | ||||
* information | * information | ||||
* | * | ||||
* @var bool | * @var bool | ||||
* @access public | * @access public | ||||
*/ | */ | ||||
public $debugMode = false; | public $debugMode = false; | ||||
/** | /** | ||||
* Save query execution statistics | * Save query execution statistics | ||||
* | * | ||||
* @var bool | * @var bool | ||||
* @access protected | * @access protected | ||||
*/ | */ | ||||
protected $_captureStatistics = false; | protected $_captureStatistics = false; | ||||
/** | /** | ||||
* Error Backoff Maximal Retry Attempts. | |||||
* | |||||
* @var integer | |||||
*/ | |||||
protected $errorBackoffMaxRetryAttempts; | |||||
/** | |||||
* Error backoff logic base time (in milliseconds). | |||||
* | |||||
* @var integer | |||||
*/ | |||||
protected $errorBackoffLogicBaseTime; | |||||
/** | |||||
* Enable lock retry debugging. | |||||
* | |||||
* @var integer | |||||
*/ | |||||
protected $enableLockRetryDebugging; | |||||
/** | |||||
* Last query to database | * Last query to database | ||||
* | * | ||||
* @var string | * @var string | ||||
* @access public | * @access public | ||||
*/ | */ | ||||
public $lastQuery = ''; | public $lastQuery = ''; | ||||
/** | /** | ||||
▲ Show 20 Lines • Show All 160 Lines • ▼ Show 20 Line(s) | |||||
* @access public | * @access public | ||||
*/ | */ | ||||
public function setup($config) | public function setup($config) | ||||
{ | { | ||||
if ( is_object($this->Application) ) { | if ( is_object($this->Application) ) { | ||||
$this->debugMode = $this->Application->isDebugMode(); | $this->debugMode = $this->Application->isDebugMode(); | ||||
} | } | ||||
$this->errorBackoffMaxRetryAttempts = $config['Database']['DBErrorBackoffMaxRetryAttempts']; | |||||
$this->errorBackoffLogicBaseTime = $config['Database']['DBErrorBackoffLogicBaseTime']; | |||||
$this->enableLockRetryDebugging = $config['Database']['DBEnableLockRetryDebugging']; | |||||
$retry = array_key_exists('DBRetry', $config['Database']) ? $config['Database']['DBRetry'] : false; | |||||
return $this->Connect( | return $this->Connect( | ||||
$config['Database']['DBHost'], | $config['Database']['DBHost'], | ||||
$config['Database']['DBUser'], | $config['Database']['DBUser'], | ||||
$config['Database']['DBUserPassword'], | $config['Database']['DBUserPassword'], | ||||
$config['Database']['DBName'] | $config['Database']['DBName'], | ||||
$retry | |||||
); | ); | ||||
} | } | ||||
/** | /** | ||||
* Performs 3 reconnect attempts in case if connection to a DB was lost in the middle of script run (e.g. server restart) | * 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 | * @return bool | ||||
* @access protected | * @access protected | ||||
Show All 27 Lines | |||||
} | } | ||||
/** | /** | ||||
* Shows error message from previous operation | * Shows error message from previous operation | ||||
* if it failed | * if it failed | ||||
* | * | ||||
* @param string $sql | * @param string $sql | ||||
* @param string $key_field | * @param string $key_field | ||||
* @param boolean|null $no_debug | * @param boolean|null $no_debug | ||||
Lint: CodingStandard.Commenting.DocComment.TagValueIndent: Tag value for @param tag indented incorrectly; expected 2 spaces but found 1 | |||||
Missing parameter comment Lint: CodingStandard.Commenting.FunctionComment.MissingParamComment: Missing parameter comment | |||||
* @param string $iterator_class | |||||
Expected 7 spaces after parameter type; 1 found Lint: CodingStandard.Commenting.FunctionComment.SpacingAfterParamType: Expected 7 spaces after parameter type; 1 found | |||||
Missing parameter comment Lint: CodingStandard.Commenting.FunctionComment.MissingParamComment: Missing parameter comment | |||||
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1 Lint: CodingStandard.Commenting.DocComment.TagValueIndent: Tag value for @param tag indented incorrectly; expected 2 spaces but found 1 | |||||
* @return bool | * @return bool | ||||
Tag @return cannot be grouped with parameter tags in a doc comment Lint: CodingStandard.Commenting.DocComment.NonParamGroup: Tag @return cannot be grouped with parameter tags in a doc comment | |||||
Expected "boolean" but found "bool" for function return type Lint: CodingStandard.Commenting.FunctionComment.InvalidReturn: Expected "boolean" but found "bool" for function return type | |||||
* @access protected | * @access protected | ||||
*/ | */ | ||||
Missing @throws tag in function comment Lint: Squiz.Commenting.FunctionCommentThrowTag.Missing: Missing @throws tag in function comment | |||||
protected function showError($sql = '', $key_field = null, $no_debug = null) | protected function showError($sql = '', $key_field = null, $no_debug = null, $iterator_class = '') | ||||
{ | { | ||||
static $retry_count = 0; | |||||
if ( $no_debug === null ) { | if ( $no_debug === null ) { | ||||
$no_debug = $this->noDebuggingState; | $no_debug = $this->noDebuggingState; | ||||
} | } | ||||
if ( !is_object($this->connectionID) ) { | if ( !is_object($this->connectionID) ) { | ||||
// no connection while doing mysql_query | // no connection while doing mysql_query | ||||
$this->errorCode = mysqli_connect_errno(); | $this->errorCode = mysqli_connect_errno(); | ||||
Show All 9 Lines | |||||
return false; | return false; | ||||
} | } | ||||
// checking if there was an error during last mysql_query | // checking if there was an error during last mysql_query | ||||
$this->errorCode = $this->connectionID->errno; | $this->errorCode = $this->connectionID->errno; | ||||
if ( $this->hasError() ) { | if ( $this->hasError() ) { | ||||
$this->erroredQuery = $sql; | |||||
$this->errorMessage = $this->connectionID->error; | $this->errorMessage = $this->connectionID->error; | ||||
$ret = $this->callErrorHandler($sql); | $recoverable_errors = array( | ||||
2006, // MySQL server has gone away. | |||||
2013, // Lost connection to MySQL server during query. | |||||
1205, // Lock wait timeout exceeded; try restarting transaction. | |||||
1213, // Deadlock found when trying to get lock; try restarting transaction. | |||||
); | |||||
if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && ($retry_count < 3) ) { | if ( in_array($this->errorCode, $recoverable_errors) ) { | ||||
// #2006 - MySQL server has gone away | try { | ||||
// #2013 - Lost connection to MySQL server during query | $ret = $this->callErrorHandler($sql, true); | ||||
$retry_count++; | } | ||||
catch ( RuntimeException $e ) { | |||||
Lint: Generic.CodeAnalysis.EmptyStatement.DetectedCatch: Empty CATCH statement detected | |||||
// Collect the exception for potential re-throwing later. | |||||
} | |||||
// Handle case, when specified error handler isn't respecting "$throw_exception" argument. | |||||
if ( !isset($e) ) { | |||||
$e = new RuntimeException(kLogger::shortenMessage(sprintf( | |||||
'%s #%d - %s. SQL: %s', | |||||
kLogger::DB_ERROR_PREFIX, | |||||
$this->errorCode, | |||||
$this->errorMessage, | |||||
trim($sql) | |||||
))); | |||||
} | |||||
if ( $this->errorRetryCount < $this->errorBackoffMaxRetryAttempts ) { | |||||
$this->errorRetryCount++; | |||||
$wait_time = $this->getErrorBackoffWaitTime($this->errorRetryCount); | |||||
usleep($wait_time * 1000); | |||||
// Write down additional info about retrying a query after a lock error. | |||||
if ( ($this->errorCode == 1205 || $this->errorCode == 1213) | |||||
&& $this->enableLockRetryDebugging | |||||
) { | |||||
$log = $this->Application->log(''); | |||||
$log->addException($e); | |||||
$cause_mapping = array( | |||||
1205 => 'Lock detected.', | |||||
1213 => 'Deadlock detected.', | |||||
); | |||||
$log->addUserData(PHP_EOL . $cause_mapping[$this->errorCode]); | |||||
$log->addUserData(sprintf( | |||||
'Retry: %d of %d.', | |||||
$this->errorRetryCount, | |||||
$this->errorBackoffMaxRetryAttempts | |||||
)); | |||||
$log->addUserData('Wait time: ' . $wait_time . 'ms'); | |||||
$log->notify(true); | |||||
$log->write(); | |||||
} | |||||
// Attempt to reconnect upon disconnect. | |||||
if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && !$this->ReConnect() ) { | |||||
if ( !$ret ) { | |||||
exit; | |||||
} | |||||
return false; | |||||
} | |||||
if ( $iterator_class ) { | |||||
return $this->GetIterator($sql, $key_field, $no_debug, $iterator_class); | |||||
} | |||||
if ( $this->ReConnect() ) { | |||||
return $this->Query($sql, $key_field, $no_debug); | return $this->Query($sql, $key_field, $no_debug); | ||||
} | } | ||||
// Show an error back to the user after all the possible retry attempts were exhausted. | |||||
throw $e; | |||||
} | } | ||||
$ret = $this->callErrorHandler($sql); | |||||
if (!$ret) { | if (!$ret) { | ||||
Expected 1 spaces after "if" opening bracket; 0 found Lint: CodingStandard.WhiteSpace.ControlStructureSpacing.SpacingAfterOpenBrace: Expected 1 spaces after "if" opening bracket; 0 found | |||||
Expected 1 spaces before "if" closing bracket; 0 found Lint: CodingStandard.WhiteSpace.ControlStructureSpacing.SpaceBeforeCloseBrace: Expected 1 spaces before "if" closing bracket; 0 found | |||||
exit; | exit; | ||||
} | } | ||||
} | } | ||||
else { | elseif ( $this->erroredQuery === $sql ) { | ||||
$retry_count = 0; | // When previously failed query is working now. | ||||
$this->erroredQuery = ''; | |||||
$this->errorRetryCount = 0; | |||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Returns the error backoff wait time in milliseconds (exponential strategy). | |||||
* | |||||
* @param integer $attempt Attempt. | |||||
* | |||||
* @return integer | |||||
*/ | |||||
protected function getErrorBackoffWaitTime($attempt) | |||||
{ | |||||
if ( $attempt == 1 ) { | |||||
return $this->errorBackoffLogicBaseTime; | |||||
} | |||||
return (int)(pow(2, $attempt) * $this->errorBackoffLogicBaseTime); | |||||
} | |||||
/** | |||||
* Sends db error to a predefined error handler | * Sends db error to a predefined error handler | ||||
* | * | ||||
* @param $sql | * @param string $sql SQL query. | ||||
* @return bool | * @param boolean|null $throw_exception Throw an exception. | ||||
* @access protected | * | ||||
* @return boolean | |||||
*/ | */ | ||||
protected function callErrorHandler($sql) | protected function callErrorHandler($sql, $throw_exception = null) | ||||
{ | { | ||||
return call_user_func($this->errorHandler, $this->errorCode, $this->errorMessage, $sql); | return call_user_func($this->errorHandler, $this->errorCode, $this->errorMessage, $sql, $throw_exception); | ||||
} | } | ||||
/** | /** | ||||
* Default error handler for sql errors | * Default error handler for sql errors | ||||
* | * | ||||
* @param int $code | * @param integer $code Error code. | ||||
* @param string $msg | * @param string $msg Error message. | ||||
* @param string $sql | * @param string $sql SQL query. | ||||
* @return bool | * @param boolean|null $throw_exception Throw an exception. | ||||
* @access public | * | ||||
* @return boolean | |||||
* @throws RuntimeException When requested. | |||||
*/ | */ | ||||
public function handleError($code, $msg, $sql) | public function handleError($code, $msg, $sql, $throw_exception = null) | ||||
{ | { | ||||
echo '<strong>Processing SQL</strong>: ' . $sql . '<br/>'; | $error_msg = '<strong>Processing SQL</strong>: ' . $sql . '<br/>'; | ||||
echo '<strong>Error (' . $code . '):</strong> ' . $msg . '<br/>'; | $error_msg .= '<strong>Error (' . $code . '):</strong> ' . $msg . '<br/>'; | ||||
if ( $throw_exception === true ) { | |||||
throw new RuntimeException(kLogger::shortenMessage($error_msg)); | |||||
} | |||||
echo $error_msg; | |||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Returns first field of first line of recordset if query ok or false otherwise. | * Returns first field of first line of recordset if query ok or false otherwise. | ||||
* | * | ||||
* @param string $sql | * @param string $sql | ||||
▲ Show 20 Lines • Show All 207 Lines • ▼ Show 20 Line(s) | |||||
else { | else { | ||||
// set 2nd checkpoint: begin | // set 2nd checkpoint: begin | ||||
if ( $this->_captureStatistics ) { | if ( $this->_captureStatistics ) { | ||||
$this->_queryTime += microtime(true) - $start_time; | $this->_queryTime += microtime(true) - $start_time; | ||||
} | } | ||||
// set 2nd checkpoint: end | // set 2nd checkpoint: end | ||||
} | } | ||||
return $this->showError($sql, $key_field, $no_debug); | return $this->showError($sql, $key_field, $no_debug, $iterator_class); | ||||
} | } | ||||
/** | /** | ||||
* Free memory used to hold recordset handle. | * Free memory used to hold recordset handle. | ||||
* | * | ||||
* @access public | * @access public | ||||
*/ | */ | ||||
public function Destroy() | public function Destroy() | ||||
▲ Show 20 Lines • Show All 510 Lines • ▼ Show 20 Line(s) | |||||
if ( $this->_profileSQLs ) { | if ( $this->_profileSQLs ) { | ||||
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); | $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); | ||||
$debugger->profilerAddTotal('sql', 'sql_' . $queryID); | $debugger->profilerAddTotal('sql', 'sql_' . $queryID); | ||||
$this->nextQueryCachable = false; | $this->nextQueryCachable = false; | ||||
} | } | ||||
// set 2nd checkpoint: end | // set 2nd checkpoint: end | ||||
} | } | ||||
return $this->showError($sql, $key_field); | return $this->showError($sql, $key_field, null, $iterator_class); | ||||
} | } | ||||
Expected 1 blank line after function; 0 found Lint: Squiz.WhiteSpace.FunctionSpacing.AfterLast: Expected 1 blank line after function; 0 found | |||||
} | } | ||||
class kMySQLQuery implements Iterator, Countable, SeekableIterator { | class kMySQLQuery implements Iterator, Countable, SeekableIterator { | ||||
/** | /** | ||||
* Current index in recordset | * Current index in recordset | ||||
* | * | ||||
▲ Show 20 Lines • Show All 278 Lines • Show Last 20 Lines |
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1