X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FDatabase.php;h=f8e579b4813b05c5972a35cf9872f5edc4b74659;hb=cdbbe0ad4aaf40c1872d204a68a014d77981e8b7;hp=df5681523673158b1f33307dca3628926b512104;hpb=d815ca352f8fde14bc00f84b2eec11c1fef7f066;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Database.php b/includes/Database.php index df56815236..f8e579b481 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -5,11 +5,6 @@ * @package MediaWiki */ -/** - * Depends on the CacheManager - */ -require_once( 'CacheManager.php' ); - /** See Database::makeList() */ define( 'LIST_COMMA', 0 ); define( 'LIST_AND', 1 ); @@ -24,8 +19,12 @@ define( 'DEADLOCK_DELAY_MIN', 500000 ); /** Maximum time to wait before retry */ define( 'DEADLOCK_DELAY_MAX', 1500000 ); +/****************************************************************************** + * Utility classes + *****************************************************************************/ + class DBObject { - var $mData; + public $mData; function DBObject($data) { $this->mData = $data; @@ -40,6 +39,190 @@ class DBObject { } }; +/****************************************************************************** + * Error classes + *****************************************************************************/ + +/** + * Database error base class + */ +class DBError extends MWException { + public $db; + + /** + * Construct a database error + * @param Database $db The database object which threw the error + * @param string $error A simple error message to be used for debugging + */ + function __construct( Database &$db, $error ) { + $this->db =& $db; + parent::__construct( $error ); + } +} + +class DBConnectionError extends DBError { + public $error; + + function __construct( Database &$db, $error = 'unknown error' ) { + $msg = 'DB connection error'; + if ( trim( $error ) != '' ) { + $msg .= ": $error"; + } + $this->error = $error; + parent::__construct( $db, $msg ); + } + + function useOutputPage() { + // Not likely to work + return false; + } + + function useMessageCache() { + // Not likely to work + return false; + } + + function getText() { + return $this->getMessage() . "\n"; + } + + function getPageTitle() { + global $wgSitename; + return "$wgSitename has a problem"; + } + + function getHTML() { + global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding; + global $wgSitename, $wgServer, $wgMessageCache, $wgLogo; + + # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky. + # Hard coding strings instead. + + $noconnect = "

Sorry! This site is experiencing technical difficulties.

Try waiting a few minutes and reloading.

(Can't contact the database server: $1)

"; + $mainpage = 'Main Page'; + $searchdisabled = <<$wgSitename search is disabled for performance reasons. You can search via Google in the meantime. +Note that their indexes of $wgSitename content may be out of date.

', +EOT; + + $googlesearch = " + +
+ +
+ +\"Google\" + + + + +
WWW $wgServer
+ + +
+
+
+"; + $cachederror = "The following is a cached copy of the requested page, and may not be up to date. "; + + # No database access + if ( is_object( $wgMessageCache ) ) { + $wgMessageCache->disable(); + } + + if ( trim( $this->error ) == '' ) { + $this->error = $this->db->getProperty('mServer'); + } + + $text = str_replace( '$1', $this->error, $noconnect ); + $text .= wfGetSiteNotice(); + + if($wgUseFileCache) { + if($wgTitle) { + $t =& $wgTitle; + } else { + if($title) { + $t = Title::newFromURL( $title ); + } elseif (@/**/$_REQUEST['search']) { + $search = $_REQUEST['search']; + return $searchdisabled . + str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ), + $wgInputEncoding ), $googlesearch ); + } else { + $t = Title::newFromText( $mainpage ); + } + } + + $cache = new CacheManager( $t ); + if( $cache->isFileCached() ) { + $msg = '

'.$msg."
\n" . + $cachederror . "

\n"; + + $tag = '
'; + $text = str_replace( + $tag, + $tag . $msg, + $cache->fetchPageText() ); + } + } + + return $text; + } +} + +class DBQueryError extends DBError { + public $error, $errno, $sql, $fname; + + function __construct( Database &$db, $error, $errno, $sql, $fname ) { + $message = "A database error has occurred\n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; + + parent::__construct( $db, $message ); + $this->error = $error; + $this->errno = $errno; + $this->sql = $sql; + $this->fname = $fname; + } + + function getText() { + if ( $this->useMessageCache() ) { + return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ), + htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n"; + } else { + return $this->getMessage(); + } + } + + function getSQL() { + global $wgShowSQLErrors; + if( !$wgShowSQLErrors ) { + return $this->msg( 'sqlhidden', 'SQL hidden' ); + } else { + return $this->sql; + } + } + + function getPageTitle() { + return $this->msg( 'databaseerror', 'Database error' ); + } + + function getHTML() { + if ( $this->useMessageCache() ) { + return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ), + htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ); + } else { + return nl2br( htmlspecialchars( $this->getMessage() ) ); + } + } +} + +class DBUnexpectedError extends DBError {} + +/******************************************************************************/ + /** * Database abstraction object * @package MediaWiki @@ -49,21 +232,18 @@ class Database { #------------------------------------------------------------------------------ # Variables #------------------------------------------------------------------------------ - /**#@+ - * @access private - */ - var $mLastQuery = ''; - var $mServer, $mUser, $mPassword, $mConn = null, $mDBname; - var $mOut, $mOpened = false; + protected $mLastQuery = ''; - var $mFailFunction; - var $mTablePrefix; - var $mFlags; - var $mTrxLevel = 0; - var $mErrorCount = 0; - var $mLBInfo = array(); - /**#@-*/ + protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; + protected $mOut, $mOpened = false; + + protected $mFailFunction; + protected $mTablePrefix; + protected $mFlags; + protected $mTrxLevel = 0; + protected $mErrorCount = 0; + protected $mLBInfo = array(); #------------------------------------------------------------------------------ # Accessors @@ -82,8 +262,8 @@ class Database { * Output page, used for reporting errors * FALSE means discard output */ - function &setOutputPage( &$out ) { - $this->mOut =& $out; + function setOutputPage( $out ) { + $this->mOut = $out; } /** @@ -118,7 +298,7 @@ class Database { /** * The current depth of nested transactions - * @param integer $level + * @param $level Integer: , default NULL. */ function trxLevel( $level = NULL ) { return wfSetVar( $this->mTrxLevel, $level ); @@ -173,11 +353,18 @@ class Database { return !!($this->mFlags & $flag); } + /** + * General read-only accessor + */ + function getProperty( $name ) { + return $this->$name; + } + #------------------------------------------------------------------------------ # Other functions #------------------------------------------------------------------------------ - /**#@+ + /**@{{ * @param string $server database server host * @param string $user database user name * @param string $password database user password @@ -187,9 +374,9 @@ class Database { /** * @param failFunction * @param $flags - * @param string $tablePrefix Database table prefixes. By default use the prefix gave in LocalSettings.php + * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php */ - function Database( $server = false, $user = false, $password = false, $dbName = false, + function __construct( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) { global $wgOut, $wgDBprefix, $wgCommandLineMode; @@ -234,7 +421,7 @@ class Database { * @param failFunction * @param $flags */ - function newFromParams( $server, $user, $password, $dbName, + static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) { return new Database( $server, $user, $password, $dbName, $failFunction, $flags ); @@ -246,16 +433,17 @@ class Database { */ function open( $server, $user, $password, $dbName ) { global $wguname; - + # Test for missing mysql.so # First try to load it if (!@extension_loaded('mysql')) { @dl('mysql.so'); } + # Fail now # Otherwise we get a suppressed fatal error, which is very hard to track down if ( !function_exists( 'mysql_connect' ) ) { - die( "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" ); + throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" ); } $this->close(); @@ -270,14 +458,7 @@ class Database { @/**/$this->mConn = mysql_pconnect( $server, $user, $password ); } else { # Create a new connection... - if( version_compare( PHP_VERSION, '4.2.0', 'ge' ) ) { - @/**/$this->mConn = mysql_connect( $server, $user, $password, true ); - } else { - # On PHP 4.1 the new_link parameter is not available. We cannot - # guarantee that we'll actually get a new connection, and this - # may cause some operations to fail possibly. - @/**/$this->mConn = mysql_connect( $server, $user, $password ); - } + @/**/$this->mConn = mysql_connect( $server, $user, $password, true ); } if ( $dbName != '' ) { @@ -302,18 +483,18 @@ class Database { if ( !$success ) { $this->reportConnectionError(); } - + global $wgDBmysql5; if( $wgDBmysql5 ) { // Tell the server we're communicating with it in UTF-8. // This may engage various charset conversions. $this->query( 'SET NAMES utf8' ); } - + $this->mOpened = $success; return $success; } - /**#@-*/ + /**@}}*/ /** * Closes a database connection. @@ -335,7 +516,6 @@ class Database { } /** - * @access private * @param string $error fallback error message, used if none is given by MySQL */ function reportConnectionError( $error = 'Unknown error' ) { @@ -343,14 +523,17 @@ class Database { if ( $myError ) { $error = $myError; } - + if ( $this->mFailFunction ) { + # Legacy error handling method if ( !is_int( $this->mFailFunction ) ) { $ff = $this->mFailFunction; $ff( $this, $error ); } } else { - wfEmergencyAbort( $this, $error ); + # New method + wfLogDBError( "Connection error: $error\n" ); + throw new DBConnectionError( $this, $error ); } } @@ -359,29 +542,24 @@ class Database { * If errors are explicitly ignored, returns success */ function query( $sql, $fname = '', $tempIgnore = false ) { - global $wgProfiling, $wgCommandLineMode; - - if ( wfReadOnly() ) { - # This is a quick check for the most common kinds of write query used - # in MediaWiki, to provide extra safety in addition to UI-level checks. - # It is not intended to prevent every conceivable write query, or even - # to handle such queries gracefully. - if ( preg_match( '/^(?:update|insert|replace|delete)/i', $sql ) ) { - wfDebug( "Write query from $fname blocked\n" ); - return false; - } - } + global $wgProfiling; if ( $wgProfiling ) { # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. # Who's been wasting my precious column space? -- TS - #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); - $profName = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); + #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); - wfProfileIn( 'Database::query' ); - wfProfileIn( $profName ); + if ( is_null( $this->getLBInfo( 'master' ) ) ) { + $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); + $totalProf = 'Database::query'; + } else { + $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); + $totalProf = 'Database::query-master'; + } + wfProfileIn( $totalProf ); + wfProfileIn( $queryProf ); } $this->mLastQuery = $sql; @@ -394,7 +572,9 @@ class Database { } # If DBO_TRX is set, start a transaction - if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() && $sql != 'BEGIN' ) { + if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() && + $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' + ) { $this->begin(); } @@ -425,8 +605,8 @@ class Database { } if ( $wgProfiling ) { - wfProfileOut( $profName ); - wfProfileOut( 'Database::query' ); + wfProfileOut( $queryProf ); + wfProfileOut( $totalProf ); } return $ret; } @@ -452,32 +632,20 @@ class Database { * @param bool $tempIgnore */ function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - global $wgCommandLineMode, $wgFullyInitialised; + global $wgCommandLineMode, $wgFullyInitialised, $wgColorErrors; # Ignore errors during error handling to avoid infinite recursion $ignore = $this->ignoreErrors( true ); ++$this->mErrorCount; if( $ignore || $tempIgnore ) { wfDebug("SQL ERROR (ignored): $error\n"); + $this->ignoreErrors( $ignore ); } else { $sql1line = str_replace( "\n", "\\n", $sql ); wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n"); wfDebug("SQL ERROR: " . $error . "\n"); - if ( $wgCommandLineMode || !$this->mOut || empty( $wgFullyInitialised ) ) { - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - if ( !$wgCommandLineMode ) { - $message = nl2br( $message ); - } - wfDebugDieBacktrace( $message ); - } else { - // this calls wfAbruptExit() - $this->mOut->databaseError( $fname, $sql, $error, $errno ); - } + throw new DBQueryError( $this, $error, $errno, $sql, $fname ); } - $this->ignoreErrors( $ignore ); } @@ -542,7 +710,6 @@ class Database { * @return string executable SQL */ function fillPrepared( $preparedQuery, $args ) { - $n = 0; reset( $args ); $this->preparedArgs =& $args; return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', @@ -556,7 +723,7 @@ class Database { * * @param array $matches * @return string - * @access private + * @private */ function fillPreparedArg( $matches ) { switch( $matches[1] ) { @@ -570,9 +737,9 @@ class Database { case '!': return $arg; case '&': # return $this->addQuotes( file_get_contents( $arg ) ); - wfDebugDieBacktrace( '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); + throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); default: - wfDebugDieBacktrace( 'Received invalid match. This should never happen!' ); + throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); } } @@ -584,7 +751,7 @@ class Database { */ function freeResult( $res ) { if ( !@/**/mysql_free_result( $res ) ) { - wfDebugDieBacktrace( "Unable to free MySQL result\n" ); + throw new DBUnexpectedError( $this, "Unable to free MySQL result" ); } } @@ -594,7 +761,7 @@ class Database { function fetchObject( $res ) { @/**/$row = mysql_fetch_object( $res ); if( mysql_errno() ) { - wfDebugDieBacktrace( 'Error in fetchObject(): ' . htmlspecialchars( mysql_error() ) ); + throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( mysql_error() ) ); } return $row; } @@ -606,7 +773,7 @@ class Database { function fetchRow( $res ) { @/**/$row = mysql_fetch_array( $res ); if (mysql_errno() ) { - wfDebugDieBacktrace( 'Error in fetchRow(): ' . htmlspecialchars( mysql_error() ) ); + throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( mysql_error() ) ); } return $row; } @@ -617,7 +784,7 @@ class Database { function numRows( $res ) { @/**/$n = mysql_num_rows( $res ); if( mysql_errno() ) { - wfDebugDieBacktrace( 'Error in numRows(): ' . htmlspecialchars( mysql_error() ) ); + throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( mysql_error() ) ); } return $n; } @@ -630,7 +797,8 @@ class Database { /** * Get a field name in a result object - * See documentation for mysql_field_name() + * See documentation for mysql_field_name(): + * http://www.php.net/mysql_field_name */ function fieldName( $res, $n ) { return mysql_field_name( $res, $n ); } @@ -705,7 +873,7 @@ class Database { $table = $this->tableName( $table ); $sql = "UPDATE $table SET $var = '" . $this->strencode( $value ) . "' WHERE ($cond)"; - return (bool)$this->query( $sql, DB_MASTER, $fname ); + return (bool)$this->query( $sql, $fname ); } /** @@ -736,7 +904,7 @@ class Database { * Returns an optional USE INDEX clause to go after the table, and a * string to go at the end of the query * - * @access private + * @private * * @param array $options an associative array of options to be turned into * an SQL query, valid keys are listed in the function. @@ -744,31 +912,43 @@ class Database { */ function makeSelectOptions( $options ) { $tailOpts = ''; + $startOpts = ''; - if ( isset( $options['GROUP BY'] ) ) { - $tailOpts .= " GROUP BY {$options['GROUP BY']}"; - } - if ( isset( $options['ORDER BY'] ) ) { - $tailOpts .= " ORDER BY {$options['ORDER BY']}"; + $noKeyOptions = array(); + foreach ( $options as $key => $option ) { + if ( is_numeric( $key ) ) { + $noKeyOptions[$option] = true; + } } + + if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}"; + if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}"; + if (isset($options['LIMIT'])) { - $tailOpts .= $this->limitResult('', $options['LIMIT'], + $tailOpts .= $this->limitResult('', $options['LIMIT'], isset($options['OFFSET']) ? $options['OFFSET'] : false); } - if ( is_numeric( array_search( 'FOR UPDATE', $options ) ) ) { - $tailOpts .= ' FOR UPDATE'; - } - if ( is_numeric( array_search( 'LOCK IN SHARE MODE', $options ) ) ) { - $tailOpts .= ' LOCK IN SHARE MODE'; - } + if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; + if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; + if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; + + # Various MySQL extensions + if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY'; + if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT'; + if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT'; + if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT'; + if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS'; + if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE'; + if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE'; if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { $useIndex = $this->useIndexClause( $options['USE INDEX'] ); } else { $useIndex = ''; } - return array( $useIndex, $tailOpts ); + + return array( $startOpts, $useIndex, $tailOpts ); } /** @@ -793,15 +973,15 @@ class Database { $from = ''; } - list( $useIndex, $tailOpts ) = $this->makeSelectOptions( $options ); + list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $options ); if( !empty( $conds ) ) { if ( is_array( $conds ) ) { $conds = $this->makeList( $conds, LIST_AND ); } - $sql = "SELECT $vars $from $useIndex WHERE $conds $tailOpts"; + $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $tailOpts"; } else { - $sql = "SELECT $vars $from $useIndex $tailOpts"; + $sql = "SELECT $startOpts $vars $from $useIndex $tailOpts"; } return $this->query( $sql, $fname ); @@ -843,7 +1023,7 @@ class Database { * @param string $sql A SQL Query * @static */ - function generalizeSQL( $sql ) { + static function generalizeSQL( $sql ) { # This does the same as the regexp below would do, but in such a way # as to avoid crashing php on some large strings. # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); @@ -870,7 +1050,7 @@ class Database { */ function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) { $table = $this->tableName( $table ); - $res = $this->query( 'DESCRIBE '.$table, DB_SLAVE, $fname ); + $res = $this->query( 'DESCRIBE '.$table, $fname ); if ( !$res ) { return NULL; } @@ -1027,7 +1207,7 @@ class Database { /** * Make UPDATE options for the Database::update function * - * @access private + * @private * @param array $options The options passed to Database::update * @return string */ @@ -1038,17 +1218,17 @@ class Database { $opts = array(); if ( in_array( 'LOW_PRIORITY', $options ) ) $opts[] = $this->lowPriorityOption(); - if ( in_array( 'IGNORE', $options ) ) + if ( in_array( 'IGNORE', $options ) ) $opts[] = 'IGNORE'; return implode(' ', $opts); } - + /** * UPDATE wrapper, takes a condition array and a SET array * * @param string $table The table to UPDATE * @param array $values An array of values to SET - * @param array $conds An array of conditions (WHERE) + * @param array $conds An array of conditions (WHERE). Use '*' to update all rows. * @param string $fname The Class::Function calling this function * (for the log) * @param array $options An array of UPDATE options, can be one or @@ -1066,14 +1246,16 @@ class Database { /** * Makes a wfStrencoded list from an array - * $mode: LIST_COMMA - comma separated, no field names + * $mode: + * LIST_COMMA - comma separated, no field names * LIST_AND - ANDed WHERE clause (without the WHERE) + * LIST_OR - ORed WHERE clause (without the WHERE) * LIST_SET - comma separated with field names, like a SET clause - * LIST_NAMES - comma separated field names + * LIST_NAMES - comma separated field names */ function makeList( $a, $mode = LIST_COMMA ) { if ( !is_array( $a ) ) { - wfDebugDieBacktrace( 'Database::makeList called with incorrect parameters' ); + throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); } $first = true; @@ -1092,7 +1274,7 @@ class Database { } if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { $list .= "($value)"; - } elseif ( $mode == LIST_AND && is_array ($value) ) { + } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array ($value) ) { $list .= $field." IN (".$this->makeList($value).") "; } else { if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { @@ -1112,25 +1294,6 @@ class Database { return mysql_select_db( $db, $this->mConn ); } - /** - * Starts a timer which will kill the DB thread after $timeout seconds - */ - function startTimer( $timeout ) { - global $IP; - if( function_exists( 'mysql_thread_id' ) ) { - # This will kill the query if it's still running after $timeout seconds. - $tid = mysql_thread_id( $this->mConn ); - exec( "php $IP/includes/killthread.php $timeout $tid &>/dev/null &" ); - } - } - - /** - * Stop a timer started by startTimer() - * Currently unimplemented. - * - */ - function stopTimer() { } - /** * Format a table name ready for use in constructing an SQL query * @@ -1179,7 +1342,7 @@ class Database { } /** - * @access private + * @private */ function tableNamesWithUseIndex( $tables, $use_index ) { $ret = array(); @@ -1199,7 +1362,7 @@ class Database { * @return string slashed string. */ function strencode( $s ) { - return addslashes( $s ); + return mysql_real_escape_string( $s, $this->mConn ); } /** @@ -1226,7 +1389,7 @@ class Database { $s=str_replace(array('%','_'),array('\%','\_'),$s); return $s; } - + /** * Returns an appropriately quoted sequence value for inserting a new row. * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL @@ -1295,7 +1458,7 @@ class Database { */ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) { if ( !$conds ) { - wfDebugDieBacktrace( 'Database::deleteJoin() called with empty $conds' ); + throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' ); } $delTable = $this->tableName( $delTable ); @@ -1340,7 +1503,7 @@ class Database { */ function delete( $table, $conds, $fname = 'Database::delete' ) { if ( !$conds ) { - wfDebugDieBacktrace( 'Database::delete() called with no conditions' ); + throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' ); } $table = $this->tableName( $table ); $sql = "DELETE FROM $table"; @@ -1357,19 +1520,29 @@ class Database { * $conds may be "*" to copy the whole table * srcTable may be an array of tables. */ - function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect' ) { + function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect', + $insertOptions = array(), $selectOptions = array() ) + { $destTable = $this->tableName( $destTable ); - if( is_array( $srcTable ) ) { - $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); + if ( is_array( $insertOptions ) ) { + $insertOptions = implode( ' ', $insertOptions ); + } + if( !is_array( $selectOptions ) ) { + $selectOptions = array( $selectOptions ); + } + list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); + if( is_array( $srcTable ) ) { + $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); } else { $srcTable = $this->tableName( $srcTable ); } - $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . - ' SELECT ' . implode( ',', $varMap ) . - " FROM $srcTable"; + $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . + " SELECT $startOpts " . implode( ',', $varMap ) . + " FROM $srcTable $useIndex "; if ( $conds != '*' ) { $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); } + $sql .= " $tailOpts"; return $this->query( $sql, $fname ); } @@ -1381,7 +1554,12 @@ class Database { * $offset integer the SQL offset (default false) */ function limitResult($sql, $limit, $offset=false) { - return " $sql LIMIT ".((is_numeric($offset) && $offset != 0)?"{$offset},":"")."{$limit} "; + if( !is_numeric($limit) ) { + throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); + } + return " $sql LIMIT " + . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" ) + . "{$limit} "; } function limitResultForUpdate($sql, $num) { return $this->limitResult($sql, $num, 0); @@ -1518,26 +1696,19 @@ class Database { } /** - * Begin a transaction, or if a transaction has already started, continue it + * Begin a transaction, committing any previously open transaction */ function begin( $fname = 'Database::begin' ) { - if ( !$this->mTrxLevel ) { - $this->immediateBegin( $fname ); - } else { - $this->mTrxLevel++; - } + $this->query( 'BEGIN', $fname ); + $this->mTrxLevel = 1; } /** - * End a transaction, or decrement the nest level if transactions are nested + * End a transaction */ function commit( $fname = 'Database::commit' ) { - if ( $this->mTrxLevel ) { - $this->mTrxLevel--; - } - if ( !$this->mTrxLevel ) { - $this->immediateCommit( $fname ); - } + $this->query( 'COMMIT', $fname ); + $this->mTrxLevel = 0; } /** @@ -1550,18 +1721,18 @@ class Database { /** * Begin a transaction, committing any previously open transaction + * @deprecated use begin() */ function immediateBegin( $fname = 'Database::immediateBegin' ) { - $this->query( 'BEGIN', $fname ); - $this->mTrxLevel = 1; + $this->begin(); } /** * Commit transaction, if one is open + * @deprecated use commit() */ function immediateCommit( $fname = 'Database::immediateCommit' ) { - $this->query( 'COMMIT', $fname ); - $this->mTrxLevel = 0; + $this->commit(); } /** @@ -1636,9 +1807,23 @@ class Database { # dubious, but unfortunately there's no easy rigorous way $slaveThreads = 0; while ( $row = $this->fetchObject( $res ) ) { - if ( $row->User == 'system user' ) { - if ( ++$slaveThreads == 2 ) { - # This is it, return the time + /* This should work for most situations - when default db + * for thread is not specified, it had no events executed, + * and therefore it doesn't know yet how lagged it is. + * + * Relay log I/O thread does not select databases. + */ + if ( $row->User == 'system user' && + $row->State != 'Waiting for master to send event' && + $row->State != 'Connecting to master' && + $row->State != 'Queueing master event to the relay log' && + $row->State != 'Waiting for master update' && + $row->State != 'Requesting binlog dump' + ) { + # This is it, return the time (except -ve) + if ( $row->Time > 0x7fffffff ) { + return false; + } else { return $row->Time; } } @@ -1649,8 +1834,8 @@ class Database { /** * Get status information from SHOW STATUS in an associative array */ - function getStatus() { - $res = $this->query( 'SHOW STATUS' ); + function getStatus($which="%") { + $res = $this->query( "SHOW STATUS LIKE '{$which}'" ); $status = array(); while ( $row = $this->fetchObject( $res ) ) { $status[$row->Variable_name] = $row->Value; @@ -1668,6 +1853,104 @@ class Database { function encodeBlob($b) { return $b; } + + function decodeBlob($b) { + return $b; + } + + /** + * Read and execute SQL commands from a file. + * Returns true on success, error string on failure + */ + function sourceFile( $filename ) { + $fp = fopen( $filename, 'r' ); + if ( false === $fp ) { + return "Could not open \"{$fname}\".\n"; + } + + $cmd = ""; + $done = false; + $dollarquote = false; + + while ( ! feof( $fp ) ) { + $line = trim( fgets( $fp, 1024 ) ); + $sl = strlen( $line ) - 1; + + if ( $sl < 0 ) { continue; } + if ( '-' == $line{0} && '-' == $line{1} ) { continue; } + + ## Allow dollar quoting for function declarations + if (substr($line,0,4) == '$mw$') { + if ($dollarquote) { + $dollarquote = false; + $done = true; + } + else { + $dollarquote = true; + } + } + else if (!$dollarquote) { + if ( ';' == $line{$sl} && ($sl < 2 || ';' != $line{$sl - 1})) { + $done = true; + $line = substr( $line, 0, $sl ); + } + } + + if ( '' != $cmd ) { $cmd .= ' '; } + $cmd .= "$line\n"; + + if ( $done ) { + $cmd = str_replace(';;', ";", $cmd); + $cmd = $this->replaceVars( $cmd ); + $res = $this->query( $cmd, 'dbsource', true ); + + if ( false === $res ) { + $err = $this->lastError(); + return "Query \"{$cmd}\" failed with error code \"$err\".\n"; + } + + $cmd = ''; + $done = false; + } + } + fclose( $fp ); + return true; + } + + /** + * Replace variables in sourced SQL + */ + protected function replaceVars( $ins ) { + $varnames = array( + 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser', + 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword', + 'wgDBadminuser', 'wgDBadminpassword', + ); + + // Ordinary variables + foreach ( $varnames as $var ) { + if( isset( $GLOBALS[$var] ) ) { + $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check? + $ins = str_replace( '{$' . $var . '}', $val, $ins ); + $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins ); + $ins = str_replace( '/*$' . $var . '*/', $val, $ins ); + } + } + + // Table prefixes + $ins = preg_replace_callback( '/\/\*(?:\$wgDBprefix|_)\*\/([a-z_]*)/', + array( &$this, 'tableNameCallback' ), $ins ); + return $ins; + } + + /** + * Table name callback + * @private + */ + protected function tableNameCallback( $matches ) { + return $this->tableName( $matches[1] ); + } + } /** @@ -1693,7 +1976,7 @@ class ResultWrapper { /** * @todo document */ - function ResultWrapper( $database, $result ) { + function ResultWrapper( &$database, $result ) { $this->db =& $database; $this->result =& $result; } @@ -1715,7 +1998,7 @@ class ResultWrapper { /** * @todo document */ - function &fetchRow() { + function fetchRow() { return $this->db->fetchRow( $this->result ); } @@ -1731,110 +2014,7 @@ class ResultWrapper { function seek( $row ) { $this->db->dataSeek( $this->result, $row ); } -} - -#------------------------------------------------------------------------------ -# Global functions -#------------------------------------------------------------------------------ - -/** - * Standard fail function, called by default when a connection cannot be - * established. - * Displays the file cache if possible - */ -function wfEmergencyAbort( &$conn, $error ) { - global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding; - global $wgSitename, $wgServer, $wgMessageCache, $wgLogo; - - # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky. - # Hard coding strings instead. - - $noconnect = "

$wgSitename has a problem

Sorry! This site is experiencing technical difficulties.

Try waiting a few minutes and reloading.

(Can't contact the database server: $1)

"; - $mainpage = 'Main Page'; - $searchdisabled = <<$wgSitename search is disabled for performance reasons. You can search via Google in the meantime. -Note that their indexes of $wgSitename content may be out of date.

', -EOT; - - $googlesearch = " - -
- -
- -\"Google\" - - - - -
WWW $wgServer
- - -
-
-
-"; - $cachederror = "The following is a cached copy of the requested page, and may not be up to date. "; - - - if( !headers_sent() ) { - header( 'HTTP/1.0 500 Internal Server Error' ); - header( 'Content-type: text/html; charset='.$wgOutputEncoding ); - /* Don't cache error pages! They cause no end of trouble... */ - header( 'Cache-control: none' ); - header( 'Pragma: nocache' ); - } - - # No database access - if ( is_object( $wgMessageCache ) ) { - $wgMessageCache->disable(); - } - - if ( trim( $error ) == '' ) { - $error = $this->mServer; - } - - wfLogDBError( "Connection error: $error\n" ); - - $msg = wfGetSiteNotice(); - if($msg == '') { - $msg = str_replace( '$1', $error, $noconnect ); - } - $text = $msg; - - if($wgUseFileCache) { - if($wgTitle) { - $t =& $wgTitle; - } else { - if($title) { - $t = Title::newFromURL( $title ); - } elseif (@/**/$_REQUEST['search']) { - $search = $_REQUEST['search']; - echo $searchdisabled; - echo str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ), - $wgInputEncoding ), $googlesearch ); - wfErrorExit(); - } else { - $t = Title::newFromText( $mainpage ); - } - } - - $cache = new CacheManager( $t ); - if( $cache->isFileCached() ) { - $msg = '

'.$msg."
\n" . - $cachederror . "

\n"; - - $tag = '
'; - $text = str_replace( - $tag, - $tag . $msg, - $cache->fetchPageText() ); - } - } - echo $text; - wfErrorExit(); } ?>