/** Maximum time to wait before retry */
define( 'DEADLOCK_DELAY_MAX', 1500000 );
-/******************************************************************************
- * Utility classes
- *****************************************************************************/
-
/**
- * Utility class.
+ * Database abstraction object
* @addtogroup Database
*/
-class DBObject {
- public $mData;
+class Database {
- function DBObject($data) {
- $this->mData = $data;
- }
+#------------------------------------------------------------------------------
+# Variables
+#------------------------------------------------------------------------------
- function isLOB() {
- return false;
- }
+ protected $mLastQuery = '';
- function data() {
- return $this->mData;
+ 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();
+ protected $mFakeSlaveLag = null, $mFakeMaster = false;
+
+#------------------------------------------------------------------------------
+# Accessors
+#------------------------------------------------------------------------------
+ # These optionally set a variable and return the previous state
+
+ /**
+ * Fail function, takes a Database as a parameter
+ * Set to false for default, 1 for ignore errors
+ */
+ function failFunction( $function = NULL ) {
+ return wfSetVar( $this->mFailFunction, $function );
}
-};
-/**
- * Utility class.
- * @addtogroup Database
- */
-class MySQLField {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_key, $type;
- function __construct ($info) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
- $this->type = $info->type;
+ /**
+ * Output page, used for reporting errors
+ * FALSE means discard output
+ */
+ function setOutputPage( $out ) {
+ $this->mOut = $out;
}
- function name() {
- return $this->name;
+ /**
+ * Boolean, controls output of large amounts of debug information
+ */
+ function debug( $debug = NULL ) {
+ return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
}
- function tableName() {
- return $this->tableName;
+ /**
+ * Turns buffering of SQL result sets on (true) or off (false).
+ * Default is "on" and it should not be changed without good reasons.
+ */
+ function bufferResults( $buffer = NULL ) {
+ if ( is_null( $buffer ) ) {
+ return !(bool)( $this->mFlags & DBO_NOBUFFER );
+ } else {
+ return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
+ }
}
- function defaultValue() {
- return $this->default;
+ /**
+ * Turns on (false) or off (true) the automatic generation and sending
+ * of a "we're sorry, but there has been a database error" page on
+ * database errors. Default is on (false). When turned off, the
+ * code should use lastErrno() and lastError() to handle the
+ * situation as appropriate.
+ */
+ function ignoreErrors( $ignoreErrors = NULL ) {
+ return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
}
- function maxLength() {
- return $this->max_length;
+ /**
+ * The current depth of nested transactions
+ * @param $level Integer: , default NULL.
+ */
+ function trxLevel( $level = NULL ) {
+ return wfSetVar( $this->mTrxLevel, $level );
}
- function nullable() {
- return $this->nullable;
+ /**
+ * Number of errors logged, only useful when errors are ignored
+ */
+ function errorCount( $count = NULL ) {
+ return wfSetVar( $this->mErrorCount, $count );
}
- function isKey() {
- return $this->is_key;
+ function tablePrefix( $prefix = null ) {
+ return wfSetVar( $this->mTablePrefix, $prefix );
}
- function isMultipleKey() {
- return $this->is_multiple;
+ /**
+ * Properties passed down from the server info array of the load balancer
+ */
+ function getLBInfo( $name = NULL ) {
+ if ( is_null( $name ) ) {
+ return $this->mLBInfo;
+ } else {
+ if ( array_key_exists( $name, $this->mLBInfo ) ) {
+ return $this->mLBInfo[$name];
+ } else {
+ return NULL;
+ }
+ }
}
- function type() {
- return $this->type;
+ function setLBInfo( $name, $value = NULL ) {
+ if ( is_null( $value ) ) {
+ $this->mLBInfo = $name;
+ } else {
+ $this->mLBInfo[$name] = $value;
+ }
}
-}
-/******************************************************************************
- * Error classes
- *****************************************************************************/
+ /**
+ * Set lag time in seconds for a fake slave
+ */
+ function setFakeSlaveLag( $lag ) {
+ $this->mFakeSlaveLag = $lag;
+ }
-/**
- * Database error base class
- * @addtogroup Database
- */
-class DBError extends MWException {
- public $db;
+ /**
+ * Make this connection a fake master
+ */
+ function setFakeMaster( $enabled = true ) {
+ $this->mFakeMaster = $enabled;
+ }
/**
- * 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
+ * Returns true if this database supports (and uses) cascading deletes
*/
- function __construct( Database &$db, $error ) {
- $this->db =& $db;
- parent::__construct( $error );
+ function cascadingDeletes() {
+ return false;
}
-}
-/**
- * @addtogroup Database
- */
-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 );
+ /**
+ * Returns true if this database supports (and uses) triggers (e.g. on the page table)
+ */
+ function cleanupTriggers() {
+ return false;
}
- function useOutputPage() {
- // Not likely to work
+ /**
+ * Returns true if this database is strict about what can be put into an IP field.
+ * Specifically, it uses a NULL value instead of an empty string.
+ */
+ function strictIPs() {
return false;
}
- function useMessageCache() {
- // Not likely to work
+ /**
+ * Returns true if this database uses timestamps rather than integers
+ */
+ function realTimestamps() {
return false;
}
-
- function getText() {
- return $this->getMessage() . "\n";
+
+ /**
+ * Returns true if this database does an implicit sort when doing GROUP BY
+ */
+ function implicitGroupby() {
+ return true;
}
- function getLogMessage() {
- # Don't send to the exception log
+ /**
+ * Returns true if this database does an implicit order by when the column has an index
+ * For example: SELECT page_title FROM page LIMIT 1
+ */
+ function implicitOrderby() {
+ return true;
+ }
+
+ /**
+ * Returns true if this database can do a native search on IP columns
+ * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
+ */
+ function searchableIPs() {
return false;
}
- function getPageTitle() {
- global $wgSitename;
- return "$wgSitename has a problem";
+ /**
+ * Returns true if this database can use functional indexes
+ */
+ function functionalIndexes() {
+ return false;
}
- function getHTML() {
- global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding;
- global $wgSitename, $wgServer, $wgMessageCache;
+ /**#@+
+ * Get function
+ */
+ function lastQuery() { return $this->mLastQuery; }
+ function isOpen() { return $this->mOpened; }
+ /**#@-*/
- # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky.
- # Hard coding strings instead.
+ function setFlag( $flag ) {
+ $this->mFlags |= $flag;
+ }
- $noconnect = "<p><strong>Sorry! This site is experiencing technical difficulties.</strong></p><p>Try waiting a few minutes and reloading.</p><p><small>(Can't contact the database server: $1)</small></p>";
- $mainpage = 'Main Page';
- $searchdisabled = <<<EOT
-<p style="margin: 1.5em 2em 1em">$wgSitename search is disabled for performance reasons. You can search via Google in the meantime.
-<span style="font-size: 89%; display: block; margin-left: .2em">Note that their indexes of $wgSitename content may be out of date.</span></p>',
-EOT;
-
- $googlesearch = "
-<!-- SiteSearch Google -->
-<FORM method=GET action=\"http://www.google.com/search\">
-<TABLE bgcolor=\"#FFFFFF\"><tr><td>
-<A HREF=\"http://www.google.com/\">
-<IMG SRC=\"http://www.google.com/logos/Logo_40wht.gif\"
-border=\"0\" ALT=\"Google\"></A>
-</td>
-<td>
-<INPUT TYPE=text name=q size=31 maxlength=255 value=\"$1\">
-<INPUT type=submit name=btnG VALUE=\"Google Search\">
-<font size=-1>
-<input type=hidden name=domains value=\"$wgServer\"><br /><input type=radio name=sitesearch value=\"\"> WWW <input type=radio name=sitesearch value=\"$wgServer\" checked> $wgServer <br />
-<input type='hidden' name='ie' value='$2'>
-<input type='hidden' name='oe' value='$2'>
-</font>
-</td></tr></TABLE>
-</FORM>
-<!-- SiteSearch Google -->";
- $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 HTMLFileCache( $t );
- if( $cache->isFileCached() ) {
- // FIXME: $msg is not defined on the next line.
- $msg = '<p style="color: red"><b>'.$msg."<br />\n" .
- $cachederror . "</b></p>\n";
-
- $tag = '<div id="article">';
- $text = str_replace(
- $tag,
- $tag . $msg,
- $cache->fetchPageText() );
- }
- }
-
- return $text;
- }
-}
-
-/**
- * @addtogroup Database
- */
-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 getLogMessage() {
- # Don't send to the exception log
- return false;
- }
-
- 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() ) );
- }
- }
-}
-
-/**
- * @addtogroup Database
- */
-class DBUnexpectedError extends DBError {}
-
-/******************************************************************************/
-
-/**
- * Database abstraction object
- * @addtogroup Database
- */
-class Database {
-
-#------------------------------------------------------------------------------
-# Variables
-#------------------------------------------------------------------------------
-
- protected $mLastQuery = '';
-
- 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
-#------------------------------------------------------------------------------
- # These optionally set a variable and return the previous state
-
- /**
- * Fail function, takes a Database as a parameter
- * Set to false for default, 1 for ignore errors
- */
- function failFunction( $function = NULL ) {
- return wfSetVar( $this->mFailFunction, $function );
- }
-
- /**
- * Output page, used for reporting errors
- * FALSE means discard output
- */
- function setOutputPage( $out ) {
- $this->mOut = $out;
- }
-
- /**
- * Boolean, controls output of large amounts of debug information
- */
- function debug( $debug = NULL ) {
- return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
- }
-
- /**
- * Turns buffering of SQL result sets on (true) or off (false).
- * Default is "on" and it should not be changed without good reasons.
- */
- function bufferResults( $buffer = NULL ) {
- if ( is_null( $buffer ) ) {
- return !(bool)( $this->mFlags & DBO_NOBUFFER );
- } else {
- return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
- }
- }
-
- /**
- * Turns on (false) or off (true) the automatic generation and sending
- * of a "we're sorry, but there has been a database error" page on
- * database errors. Default is on (false). When turned off, the
- * code should use lastErrno() and lastError() to handle the
- * situation as appropriate.
- */
- function ignoreErrors( $ignoreErrors = NULL ) {
- return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
- }
-
- /**
- * The current depth of nested transactions
- * @param $level Integer: , default NULL.
- */
- function trxLevel( $level = NULL ) {
- return wfSetVar( $this->mTrxLevel, $level );
- }
-
- /**
- * Number of errors logged, only useful when errors are ignored
- */
- function errorCount( $count = NULL ) {
- return wfSetVar( $this->mErrorCount, $count );
- }
-
- /**
- * Properties passed down from the server info array of the load balancer
- */
- function getLBInfo( $name = NULL ) {
- if ( is_null( $name ) ) {
- return $this->mLBInfo;
- } else {
- if ( array_key_exists( $name, $this->mLBInfo ) ) {
- return $this->mLBInfo[$name];
- } else {
- return NULL;
- }
- }
- }
-
- function setLBInfo( $name, $value = NULL ) {
- if ( is_null( $value ) ) {
- $this->mLBInfo = $name;
- } else {
- $this->mLBInfo[$name] = $value;
- }
- }
-
- /**
- * Returns true if this database supports (and uses) cascading deletes
- */
- function cascadingDeletes() {
- return false;
- }
-
- /**
- * Returns true if this database supports (and uses) triggers (e.g. on the page table)
- */
- function cleanupTriggers() {
- return false;
- }
-
- /**
- * Returns true if this database is strict about what can be put into an IP field.
- * Specifically, it uses a NULL value instead of an empty string.
- */
- function strictIPs() {
- return false;
- }
-
- /**
- * Returns true if this database uses timestamps rather than integers
- */
- function realTimestamps() {
- return false;
- }
-
- /**
- * Returns true if this database does an implicit sort when doing GROUP BY
- */
- function implicitGroupby() {
- return true;
- }
-
- /**
- * Returns true if this database can do a native search on IP columns
- * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
- */
- function searchableIPs() {
- return false;
- }
-
- /**#@+
- * Get function
- */
- function lastQuery() { return $this->mLastQuery; }
- function isOpen() { return $this->mOpened; }
- /**#@-*/
-
- function setFlag( $flag ) {
- $this->mFlags |= $flag;
- }
-
- function clearFlag( $flag ) {
- $this->mFlags &= ~$flag;
- }
+ function clearFlag( $flag ) {
+ $this->mFlags &= ~$flag;
+ }
function getFlag( $flag ) {
return !!($this->mFlags & $flag);
* If the failFunction is set to a non-zero integer, returns success
*/
function open( $server, $user, $password, $dbName ) {
- global $wguname;
+ global $wguname, $wgAllDBsAreLocalhost;
wfProfileIn( __METHOD__ );
# Test for missing mysql.so
throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
}
+ # Debugging hack -- fake cluster
+ if ( $wgAllDBsAreLocalhost ) {
+ $realServer = 'localhost';
+ } else {
+ $realServer = $server;
+ }
$this->close();
$this->mServer = $server;
$this->mUser = $user;
$success = false;
wfProfileIn("dbconnect-$server");
-
- # LIVE PATCH by Tim, ask Domas for why: retry loop
+
+ # Try to connect up to three times
+ # The kernel's default SYN retransmission period is far too slow for us,
+ # so we use a short timeout plus a manual retry.
$this->mConn = false;
$max = 3;
for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
usleep( 1000 );
}
if ( $this->mFlags & DBO_PERSISTENT ) {
- @/**/$this->mConn = mysql_pconnect( $server, $user, $password );
+ @/**/$this->mConn = mysql_pconnect( $realServer, $user, $password );
} else {
# Create a new connection...
- @/**/$this->mConn = mysql_connect( $server, $user, $password, true );
+ @/**/$this->mConn = mysql_connect( $realServer, $user, $password, true );
}
if ($this->mConn === false) {
#$iplus = $i + 1;
* @throws DBQueryError Thrown when the database returns an error of any kind
*/
public function query( $sql, $fname = '', $tempIgnore = false ) {
- global $wgProfiling;
+ global $wgProfiler;
- if ( $wgProfiling ) {
+ $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+ if ( isset( $wgProfiler ) ) {
# 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 );
- if ( is_null( $this->getLBInfo( 'master' ) ) ) {
- $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'Database::query';
- } else {
+ if ( $isMaster ) {
$queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
$totalProf = 'Database::query-master';
+ } else {
+ $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'Database::query';
}
wfProfileIn( $totalProf );
wfProfileIn( $queryProf );
global $wgUser;
if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) {
$userName = $wgUser->getName();
- if ( strlen( $userName ) > 15 ) {
- $userName = substr( $userName, 0, 15 ) . '...';
+ if ( mb_strlen( $userName ) > 15 ) {
+ $userName = mb_substr( $userName, 0, 15 ) . '...';
}
$userName = str_replace( '/', '', $userName );
} else {
# If DBO_TRX is set, start a transaction
if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
- ) {
- $this->begin();
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') {
+ // avoid establishing transactions for SHOW and SET statements too -
+ // that would delay transaction initializations to once connection
+ // is really used by application
+ $sqlstart = substr($sql,0,10); // very much worth it, benchmark certified(tm)
+ if (strpos($sqlstart,"SHOW ")!==0 and strpos($sqlstart,"SET ")!==0)
+ $this->begin();
}
if ( $this->debug() ) {
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
- wfDebug( "SQL: $sqlx\n" );
+ if ( $isMaster ) {
+ wfDebug( "SQL-master: $sqlx\n" );
+ } else {
+ wfDebug( "SQL: $sqlx\n" );
+ }
}
# Do the query and handle errors
$this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
}
- if ( $wgProfiling ) {
+ if ( isset( $wgProfiler ) ) {
wfProfileOut( $queryProf );
wfProfileOut( $totalProf );
}
* (for the log)
* @param array $options An array of UPDATE options, can be one or
* more of IGNORE, LOW_PRIORITY
+ * @return bool
*/
function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
$table = $this->tableName( $table );
if ( $conds != '*' ) {
$sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
- $this->query( $sql, $fname );
+ return $this->query( $sql, $fname );
}
/**
} elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
$list .= "$value";
} elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
- $list .= $field." IN (".$this->makeList($value).") ";
+ if( count( $value ) == 0 ) {
+ throw new MWException( __METHOD__.': empty input' );
+ } elseif( count( $value ) == 1 ) {
+ // Special-case single values, as IN isn't terribly efficient
+ // Don't necessarily assume the single key is 0; we don't
+ // enforce linear numeric ordering on other arrays here.
+ $value = array_values( $value );
+ $list .= $field." = ".$this->addQuotes( $value[0] );
+ } else {
+ $list .= $field." IN (".$this->makeList($value).") ";
+ }
} elseif( is_null($value) ) {
if ( $mode == LIST_AND || $mode == LIST_OR ) {
$list .= "$field IS ";
return mysql_select_db( $db, $this->mConn );
}
+ /**
+ * Get the current DB name
+ */
+ function getDBname() {
+ return $this->mDBname;
+ }
+
+ /**
+ * Get the server hostname or IP address
+ */
+ function getServer() {
+ return $this->mServer;
+ }
+
/**
* Format a table name ready for use in constructing an SQL query
*
global $wgSharedDB;
# Skip quoted literals
if ( $name{0} != '`' ) {
- if ( $this->mTablePrefix !== '' && strpos( '.', $name ) === false ) {
+ if ( $this->mTablePrefix !== '' && strpos( $name, '.' ) === false ) {
$name = "{$this->mTablePrefix}$name";
}
if ( isset( $wgSharedDB ) && "{$this->mTablePrefix}user" == $name ) {
if( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
- $srcTable = $this->tableName( $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 );
+ $srcTable = $this->tableName( $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 );
+ }
+
+ /**
+ * Construct a LIMIT query with optional offset
+ * This is used for query pages
+ * $sql string SQL query we will append the limit too
+ * $limit integer the SQL limit
+ * $offset integer the SQL offset (default false)
+ */
+ function limitResult($sql, $limit, $offset=false) {
+ 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);
+ }
+
+ /**
+ * Returns an SQL expression for a simple conditional.
+ * Uses IF on MySQL.
+ *
+ * @param string $cond SQL expression which will result in a boolean value
+ * @param string $trueVal SQL expression to return if true
+ * @param string $falseVal SQL expression to return if false
+ * @return string SQL fragment
+ */
+ function conditional( $cond, $trueVal, $falseVal ) {
+ return " IF($cond, $trueVal, $falseVal) ";
+ }
+
+ /**
+ * Determines if the last failure was due to a deadlock
+ */
+ function wasDeadlock() {
+ return $this->lastErrno() == 1213;
+ }
+
+ /**
+ * Perform a deadlock-prone transaction.
+ *
+ * This function invokes a callback function to perform a set of write
+ * queries. If a deadlock occurs during the processing, the transaction
+ * will be rolled back and the callback function will be called again.
+ *
+ * Usage:
+ * $dbw->deadlockLoop( callback, ... );
+ *
+ * Extra arguments are passed through to the specified callback function.
+ *
+ * Returns whatever the callback function returned on its successful,
+ * iteration, or false on error, for example if the retry limit was
+ * reached.
+ */
+ function deadlockLoop() {
+ $myFname = 'Database::deadlockLoop';
+
+ $this->begin();
+ $args = func_get_args();
+ $function = array_shift( $args );
+ $oldIgnore = $this->ignoreErrors( true );
+ $tries = DEADLOCK_TRIES;
+ if ( is_array( $function ) ) {
+ $fname = $function[0];
+ } else {
+ $fname = $function;
+ }
+ do {
+ $retVal = call_user_func_array( $function, $args );
+ $error = $this->lastError();
+ $errno = $this->lastErrno();
+ $sql = $this->lastQuery();
+
+ if ( $errno ) {
+ if ( $this->wasDeadlock() ) {
+ # Retry
+ usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
+ } else {
+ $this->reportQueryError( $error, $errno, $sql, $fname );
+ }
+ }
+ } while( $this->wasDeadlock() && --$tries > 0 );
+ $this->ignoreErrors( $oldIgnore );
+ if ( $tries <= 0 ) {
+ $this->query( 'ROLLBACK', $myFname );
+ $this->reportQueryError( $error, $errno, $sql, $fname );
+ return false;
+ } else {
+ $this->query( 'COMMIT', $myFname );
+ return $retVal;
+ }
+ }
+
+ /**
+ * Do a SELECT MASTER_POS_WAIT()
+ *
+ * @param string $file the binlog file
+ * @param string $pos the binlog position
+ * @param integer $timeout the maximum number of seconds to wait for synchronisation
+ */
+ function masterPosWait( MySQLMasterPos $pos, $timeout ) {
+ $fname = 'Database::masterPosWait';
+ wfProfileIn( $fname );
+
+ # Commit any open transactions
+ if ( $this->mTrxLevel ) {
+ $this->immediateCommit();
+ }
+
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ $wait = intval( ( $pos->pos - microtime(true) + $this->mFakeSlaveLag ) * 1e6 );
+ if ( $wait > $timeout * 1e6 ) {
+ wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
+ wfProfileOut( $fname );
+ return -1;
+ } elseif ( $wait > 0 ) {
+ wfDebug( "Fake slave waiting $wait us\n" );
+ usleep( $wait );
+ wfProfileOut( $fname );
+ return 1;
+ } else {
+ wfDebug( "Fake slave up to date ($wait us)\n" );
+ wfProfileOut( $fname );
+ return 0;
+ }
+ }
+
+ # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
+ $encFile = $this->addQuotes( $pos->file );
+ $encPos = intval( $pos->pos );
+ $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
+ $res = $this->doQuery( $sql );
+ if ( $res && $row = $this->fetchRow( $res ) ) {
+ $this->freeResult( $res );
+ wfProfileOut( $fname );
+ return $row[0];
+ } else {
+ wfProfileOut( $fname );
+ return false;
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW SLAVE STATUS
+ */
+ function getSlavePos() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ $pos = new MySQLMasterPos( 'fake', microtime(true) - $this->mFakeSlaveLag );
+ wfDebug( __METHOD__.": fake slave pos = $pos\n" );
+ return $pos;
+ }
+ $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
+ $row = $this->fetchObject( $res );
+ if ( $row ) {
+ return new MySQLMasterPos( $row->Master_Log_File, $row->Read_Master_Log_Pos );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW MASTER STATUS
+ */
+ function getMasterPos() {
+ if ( $this->mFakeMaster ) {
+ return new MySQLMasterPos( 'fake', microtime( true ) );
+ }
+ $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
+ $row = $this->fetchObject( $res );
+ if ( $row ) {
+ return new MySQLMasterPos( $row->File, $row->Position );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Begin a transaction, committing any previously open transaction
+ */
+ function begin( $fname = 'Database::begin' ) {
+ $this->query( 'BEGIN', $fname );
+ $this->mTrxLevel = 1;
+ }
+
+ /**
+ * End a transaction
+ */
+ function commit( $fname = 'Database::commit' ) {
+ $this->query( 'COMMIT', $fname );
+ $this->mTrxLevel = 0;
+ }
+
+ /**
+ * Rollback a transaction.
+ * No-op on non-transactional databases.
+ */
+ function rollback( $fname = 'Database::rollback' ) {
+ $this->query( 'ROLLBACK', $fname, true );
+ $this->mTrxLevel = 0;
+ }
+
+ /**
+ * Begin a transaction, committing any previously open transaction
+ * @deprecated use begin()
+ */
+ function immediateBegin( $fname = 'Database::immediateBegin' ) {
+ $this->begin();
+ }
+
+ /**
+ * Commit transaction, if one is open
+ * @deprecated use commit()
+ */
+ function immediateCommit( $fname = 'Database::immediateCommit' ) {
+ $this->commit();
+ }
+
+ /**
+ * Return MW-style timestamp used for MySQL schema
+ */
+ function timestamp( $ts=0 ) {
+ return wfTimestamp(TS_MW,$ts);
+ }
+
+ /**
+ * Local database timestamp format or null
+ */
+ function timestampOrNull( $ts = null ) {
+ if( is_null( $ts ) ) {
+ return null;
+ } else {
+ return $this->timestamp( $ts );
+ }
+ }
+
+ /**
+ * @todo document
+ */
+ function resultObject( $result ) {
+ if( empty( $result ) ) {
+ return false;
+ } elseif ( $result instanceof ResultWrapper ) {
+ return $result;
+ } elseif ( $result === true ) {
+ // Successful write query
+ return $result;
+ } else {
+ return new ResultWrapper( $this, $result );
}
- $sql .= " $tailOpts";
- return $this->query( $sql, $fname );
}
/**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
- * $sql string SQL query we will append the limit too
- * $limit integer the SQL limit
- * $offset integer the SQL offset (default false)
+ * Return aggregated value alias
*/
- function limitResult($sql, $limit, $offset=false) {
- 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);
+ function aggregateValue ($valuedata,$valuename='value') {
+ return $valuename;
}
/**
- * Returns an SQL expression for a simple conditional.
- * Uses IF on MySQL.
- *
- * @param string $cond SQL expression which will result in a boolean value
- * @param string $trueVal SQL expression to return if true
- * @param string $falseVal SQL expression to return if false
- * @return string SQL fragment
+ * @return string wikitext of a link to the server software's web site
*/
- function conditional( $cond, $trueVal, $falseVal ) {
- return " IF($cond, $trueVal, $falseVal) ";
+ function getSoftwareLink() {
+ return "[http://www.mysql.com/ MySQL]";
}
/**
- * Determines if the last failure was due to a deadlock
+ * @return string Version information from the database
*/
- function wasDeadlock() {
- return $this->lastErrno() == 1213;
+ function getServerVersion() {
+ return mysql_get_server_info( $this->mConn );
}
/**
- * Perform a deadlock-prone transaction.
- *
- * This function invokes a callback function to perform a set of write
- * queries. If a deadlock occurs during the processing, the transaction
- * will be rolled back and the callback function will be called again.
- *
- * Usage:
- * $dbw->deadlockLoop( callback, ... );
- *
- * Extra arguments are passed through to the specified callback function.
- *
- * Returns whatever the callback function returned on its successful,
- * iteration, or false on error, for example if the retry limit was
- * reached.
+ * Ping the server and try to reconnect if it there is no connection
*/
- function deadlockLoop() {
- $myFname = 'Database::deadlockLoop';
+ function ping() {
+ if( !function_exists( 'mysql_ping' ) ) {
+ wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" );
+ return true;
+ }
+ $ping = mysql_ping( $this->mConn );
+ if ( $ping ) {
+ return true;
+ }
- $this->begin();
- $args = func_get_args();
- $function = array_shift( $args );
- $oldIgnore = $this->ignoreErrors( true );
- $tries = DEADLOCK_TRIES;
- if ( is_array( $function ) ) {
- $fname = $function[0];
- } else {
- $fname = $function;
+ // Need to reconnect manually in MySQL client 5.0.13+
+ if ( version_compare( mysql_get_client_info(), '5.0.13', '>=' ) ) {
+ mysql_close( $this->mConn );
+ $this->mOpened = false;
+ $this->mConn = false;
+ $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+ return true;
}
- do {
- $retVal = call_user_func_array( $function, $args );
- $error = $this->lastError();
- $errno = $this->lastErrno();
- $sql = $this->lastQuery();
+ return false;
+ }
- if ( $errno ) {
- if ( $this->wasDeadlock() ) {
- # Retry
- usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
+ /**
+ * Get slave lag.
+ * At the moment, this will only work if the DB user has the PROCESS privilege
+ */
+ function getLag() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
+ return $this->mFakeSlaveLag;
+ }
+ $res = $this->query( 'SHOW PROCESSLIST' );
+ # Find slave SQL thread
+ while ( $row = $this->fetchObject( $res ) ) {
+ /* 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 {
- $this->reportQueryError( $error, $errno, $sql, $fname );
+ return $row->Time;
}
}
- } while( $this->wasDeadlock() && --$tries > 0 );
- $this->ignoreErrors( $oldIgnore );
- if ( $tries <= 0 ) {
- $this->query( 'ROLLBACK', $myFname );
- $this->reportQueryError( $error, $errno, $sql, $fname );
- return false;
- } else {
- $this->query( 'COMMIT', $myFname );
- return $retVal;
}
+ return false;
+ }
+
+ /**
+ * Get status information from SHOW STATUS in an associative array
+ */
+ function getStatus($which="%") {
+ $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+ $status = array();
+ while ( $row = $this->fetchObject( $res ) ) {
+ $status[$row->Variable_name] = $row->Value;
+ }
+ return $status;
}
/**
- * Do a SELECT MASTER_POS_WAIT()
- *
- * @param string $file the binlog file
- * @param string $pos the binlog position
- * @param integer $timeout the maximum number of seconds to wait for synchronisation
+ * Return the maximum number of items allowed in a list, or 0 for unlimited.
*/
- function masterPosWait( $file, $pos, $timeout ) {
- $fname = 'Database::masterPosWait';
- wfProfileIn( $fname );
+ function maxListLen() {
+ return 0;
+ }
+ function encodeBlob($b) {
+ return $b;
+ }
- # Commit any open transactions
- $this->immediateCommit();
+ function decodeBlob($b) {
+ return $b;
+ }
- # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
- $encFile = $this->strencode( $file );
- $sql = "SELECT MASTER_POS_WAIT('$encFile', $pos, $timeout)";
- $res = $this->doQuery( $sql );
- if ( $res && $row = $this->fetchRow( $res ) ) {
- $this->freeResult( $res );
- wfProfileOut( $fname );
- return $row[0];
- } else {
- wfProfileOut( $fname );
- return false;
+ /**
+ * Override database's default connection timeout.
+ * May be useful for very long batch queries such as
+ * full-wiki dumps, where a single query reads out
+ * over hours or days.
+ * @param int $timeout in seconds
+ */
+ public function setTimeout( $timeout ) {
+ $this->query( "SET net_read_timeout=$timeout" );
+ $this->query( "SET net_write_timeout=$timeout" );
+ }
+
+ /**
+ * Read and execute SQL commands from a file.
+ * Returns true on success, error string on failure
+ * @param string $filename File name to open
+ * @param callback $lineCallback Optional function called before reading each line
+ * @param callback $resultCallback Optional function called for each MySQL result
+ */
+ function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
+ $fp = fopen( $filename, 'r' );
+ if ( false === $fp ) {
+ return "Could not open \"{$filename}\".\n";
}
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
+ fclose( $fp );
+ return $error;
}
/**
- * Get the position of the master from SHOW SLAVE STATUS
+ * Read and execute commands from an open file handle
+ * Returns true on success, error string on failure
+ * @param string $fp File handle
+ * @param callback $lineCallback Optional function called before reading each line
+ * @param callback $resultCallback Optional function called for each MySQL result
*/
- function getSlavePos() {
- $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
- $row = $this->fetchObject( $res );
- if ( $row ) {
- return array( $row->Master_Log_File, $row->Read_Master_Log_Pos );
- } else {
- return array( false, false );
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
+ $cmd = "";
+ $done = false;
+ $dollarquote = false;
+
+ while ( ! feof( $fp ) ) {
+ if ( $lineCallback ) {
+ call_user_func( $lineCallback );
+ }
+ $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, __METHOD__, true );
+ if ( $resultCallback ) {
+ call_user_func( $resultCallback, $res );
+ }
+
+ if ( false === $res ) {
+ $err = $this->lastError();
+ return "Query \"{$cmd}\" failed with error code \"$err\".\n";
+ }
+
+ $cmd = '';
+ $done = false;
+ }
}
+ return true;
}
+
/**
- * Get the position of the master from SHOW MASTER STATUS
+ * Replace variables in sourced SQL
*/
- function getMasterPos() {
- $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
- $row = $this->fetchObject( $res );
- if ( $row ) {
- return array( $row->File, $row->Position );
- } else {
- return array( false, false );
+ protected function replaceVars( $ins ) {
+ $varnames = array(
+ 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
+ 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
+ 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
+ );
+
+ // 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;
}
/**
- * Begin a transaction, committing any previously open transaction
+ * Table name callback
+ * @private
*/
- function begin( $fname = 'Database::begin' ) {
- $this->query( 'BEGIN', $fname );
- $this->mTrxLevel = 1;
+ protected function tableNameCallback( $matches ) {
+ return $this->tableName( $matches[1] );
}
- /**
- * End a transaction
- */
- function commit( $fname = 'Database::commit' ) {
- $this->query( 'COMMIT', $fname );
- $this->mTrxLevel = 0;
+ /*
+ * Build a concatenation list to feed into a SQL query
+ */
+ function buildConcat( $stringList ) {
+ return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
- /**
- * Rollback a transaction
- */
- function rollback( $fname = 'Database::rollback' ) {
- $this->query( 'ROLLBACK', $fname );
- $this->mTrxLevel = 0;
+}
+
+/**
+ * Database abstraction object for mySQL
+ * Inherit all methods and properties of Database::Database()
+ *
+ * @addtogroup Database
+ * @see Database
+ */
+class DatabaseMysql extends Database {
+ # Inherit all
+}
+
+/******************************************************************************
+ * Utility classes
+ *****************************************************************************/
+
+/**
+ * Utility class.
+ * @addtogroup Database
+ */
+class DBObject {
+ public $mData;
+
+ function DBObject($data) {
+ $this->mData = $data;
}
- /**
- * Begin a transaction, committing any previously open transaction
- * @deprecated use begin()
- */
- function immediateBegin( $fname = 'Database::immediateBegin' ) {
- $this->begin();
+ function isLOB() {
+ return false;
}
- /**
- * Commit transaction, if one is open
- * @deprecated use commit()
- */
- function immediateCommit( $fname = 'Database::immediateCommit' ) {
- $this->commit();
+ function data() {
+ return $this->mData;
}
+}
- /**
- * Return MW-style timestamp used for MySQL schema
- */
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_MW,$ts);
+/**
+ * Utility class
+ * @addtogroup Database
+ *
+ * This allows us to distinguish a blob from a normal string and an array of strings
+ */
+class Blob {
+ private $mData;
+ function __construct($data) {
+ $this->mData = $data;
}
+ function fetch() {
+ return $this->mData;
+ }
+}
- /**
- * Local database timestamp format or null
- */
- function timestampOrNull( $ts = null ) {
- if( is_null( $ts ) ) {
- return null;
- } else {
- return $this->timestamp( $ts );
- }
+/**
+ * Utility class.
+ * @addtogroup Database
+ */
+class MySQLField {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_key, $type;
+ function __construct ($info) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
+ $this->type = $info->type;
}
- /**
- * @todo document
- */
- function resultObject( $result ) {
- if( empty( $result ) ) {
- return false;
- } elseif ( $result instanceof ResultWrapper ) {
- return $result;
- } elseif ( $result === true ) {
- // Successful write query
- return $result;
- } else {
- return new ResultWrapper( $this, $result );
- }
+ function name() {
+ return $this->name;
}
- /**
- * Return aggregated value alias
- */
- function aggregateValue ($valuedata,$valuename='value') {
- return $valuename;
+ function tableName() {
+ return $this->tableName;
}
- /**
- * @return string wikitext of a link to the server software's web site
- */
- function getSoftwareLink() {
- return "[http://www.mysql.com/ MySQL]";
+ function defaultValue() {
+ return $this->default;
}
- /**
- * @return string Version information from the database
- */
- function getServerVersion() {
- return mysql_get_server_info( $this->mConn );
+ function maxLength() {
+ return $this->max_length;
}
- /**
- * Ping the server and try to reconnect if it there is no connection
- */
- function ping() {
- if( function_exists( 'mysql_ping' ) ) {
- return mysql_ping( $this->mConn );
- } else {
- wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" );
- return true;
- }
+ function nullable() {
+ return $this->nullable;
}
- /**
- * Get slave lag.
- * At the moment, this will only work if the DB user has the PROCESS privilege
- */
- function getLag() {
- $res = $this->query( 'SHOW PROCESSLIST' );
- # Find slave SQL thread
- while ( $row = $this->fetchObject( $res ) ) {
- /* 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;
- }
- }
- }
- return false;
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ function type() {
+ return $this->type;
}
+}
+
+/******************************************************************************
+ * Error classes
+ *****************************************************************************/
+
+/**
+ * Database error base class
+ * @addtogroup Database
+ */
+class DBError extends MWException {
+ public $db;
/**
- * Get status information from SHOW STATUS in an associative array
+ * 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 getStatus($which="%") {
- $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
- $status = array();
- while ( $row = $this->fetchObject( $res ) ) {
- $status[$row->Variable_name] = $row->Value;
+ function __construct( Database &$db, $error ) {
+ $this->db =& $db;
+ parent::__construct( $error );
+ }
+}
+
+/**
+ * @addtogroup Database
+ */
+class DBConnectionError extends DBError {
+ public $error;
+
+ function __construct( Database &$db, $error = 'unknown error' ) {
+ $msg = 'DB connection error';
+ if ( trim( $error ) != '' ) {
+ $msg .= ": $error";
}
- return $status;
+ $this->error = $error;
+ parent::__construct( $db, $msg );
}
- /**
- * Return the maximum number of items allowed in a list, or 0 for unlimited.
- */
- function maxListLen() {
- return 0;
+ function useOutputPage() {
+ // Not likely to work
+ return false;
}
- function encodeBlob($b) {
- return $b;
+ function useMessageCache() {
+ // Not likely to work
+ return false;
}
-
- function decodeBlob($b) {
- return $b;
+
+ function getText() {
+ return $this->getMessage() . "\n";
}
- /**
- * Override database's default connection timeout.
- * May be useful for very long batch queries such as
- * full-wiki dumps, where a single query reads out
- * over hours or days.
- * @param int $timeout in seconds
- */
- public function setTimeout( $timeout ) {
- $this->query( "SET net_read_timeout=$timeout" );
- $this->query( "SET net_write_timeout=$timeout" );
+ function getLogMessage() {
+ # Don't send to the exception log
+ return false;
}
- /**
- * Read and execute SQL commands from a file.
- * Returns true on success, error string on failure
- * @param string $filename File name to open
- * @param callback $lineCallback Optional function called before reading each line
- * @param callback $resultCallback Optional function called for each MySQL result
- */
- function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
- $fp = fopen( $filename, 'r' );
- if ( false === $fp ) {
- return "Could not open \"{$filename}\".\n";
- }
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
- fclose( $fp );
- return $error;
+ function getPageTitle() {
+ global $wgSitename;
+ return "$wgSitename has a problem";
}
- /**
- * Read and execute commands from an open file handle
- * Returns true on success, error string on failure
- * @param string $fp File handle
- * @param callback $lineCallback Optional function called before reading each line
- * @param callback $resultCallback Optional function called for each MySQL result
- */
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
- $cmd = "";
- $done = false;
- $dollarquote = false;
+ function getHTML() {
+ global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding;
+ global $wgSitename, $wgServer, $wgMessageCache;
- while ( ! feof( $fp ) ) {
- if ( $lineCallback ) {
- call_user_func( $lineCallback );
- }
- $line = trim( fgets( $fp, 1024 ) );
- $sl = strlen( $line ) - 1;
+ # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky.
+ # Hard coding strings instead.
- if ( $sl < 0 ) { continue; }
- if ( '-' == $line{0} && '-' == $line{1} ) { continue; }
+ $noconnect = "<p><strong>Sorry! This site is experiencing technical difficulties.</strong></p><p>Try waiting a few minutes and reloading.</p><p><small>(Can't contact the database server: $1)</small></p>";
+ $mainpage = 'Main Page';
+ $searchdisabled = <<<EOT
+<p style="margin: 1.5em 2em 1em">$wgSitename search is disabled for performance reasons. You can search via Google in the meantime.
+<span style="font-size: 89%; display: block; margin-left: .2em">Note that their indexes of $wgSitename content may be out of date.</span></p>',
+EOT;
- ## 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 );
- }
- }
+ $googlesearch = "
+<!-- SiteSearch Google -->
+<FORM method=GET action=\"http://www.google.com/search\">
+<TABLE bgcolor=\"#FFFFFF\"><tr><td>
+<A HREF=\"http://www.google.com/\">
+<IMG SRC=\"http://www.google.com/logos/Logo_40wht.gif\"
+border=\"0\" ALT=\"Google\"></A>
+</td>
+<td>
+<INPUT TYPE=text name=q size=31 maxlength=255 value=\"$1\">
+<INPUT type=submit name=btnG VALUE=\"Google Search\">
+<font size=-1>
+<input type=hidden name=domains value=\"$wgServer\"><br /><input type=radio name=sitesearch value=\"\"> WWW <input type=radio name=sitesearch value=\"$wgServer\" checked> $wgServer <br />
+<input type='hidden' name='ie' value='$2'>
+<input type='hidden' name='oe' value='$2'>
+</font>
+</td></tr></TABLE>
+</FORM>
+<!-- SiteSearch Google -->";
+ $cachederror = "The following is a cached copy of the requested page, and may not be up to date. ";
- if ( '' != $cmd ) { $cmd .= ' '; }
- $cmd .= "$line\n";
+ # No database access
+ if ( is_object( $wgMessageCache ) ) {
+ $wgMessageCache->disable();
+ }
- if ( $done ) {
- $cmd = str_replace(';;', ";", $cmd);
- $cmd = $this->replaceVars( $cmd );
- $res = $this->query( $cmd, __METHOD__, true );
- if ( $resultCallback ) {
- call_user_func( $resultCallback, $res );
- }
+ if ( trim( $this->error ) == '' ) {
+ $this->error = $this->db->getProperty('mServer');
+ }
- if ( false === $res ) {
- $err = $this->lastError();
- return "Query \"{$cmd}\" failed with error code \"$err\".\n";
+ $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 );
}
+ }
- $cmd = '';
- $done = false;
+ $cache = new HTMLFileCache( $t );
+ if( $cache->isFileCached() ) {
+ // @todo, FIXME: $msg is not defined on the next line.
+ $msg = '<p style="color: red"><b>'.$msg."<br />\n" .
+ $cachederror . "</b></p>\n";
+
+ $tag = '<div id="article">';
+ $text = str_replace(
+ $tag,
+ $tag . $msg,
+ $cache->fetchPageText() );
}
}
- return true;
+
+ return $text;
}
+}
+/**
+ * @addtogroup Database
+ */
+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";
- /**
- * Replace variables in sourced SQL
- */
- protected function replaceVars( $ins ) {
- $varnames = array(
- 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
- 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
- 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
- );
+ parent::__construct( $db, $message );
+ $this->error = $error;
+ $this->errno = $errno;
+ $this->sql = $sql;
+ $this->fname = $fname;
+ }
- // 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 );
- }
+ function getText() {
+ if ( $this->useMessageCache() ) {
+ return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
+ htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
+ } else {
+ return $this->getMessage();
}
-
- // Table prefixes
- $ins = preg_replace_callback( '/\/\*(?:\$wgDBprefix|_)\*\/([a-z_]*)/',
- array( &$this, 'tableNameCallback' ), $ins );
- return $ins;
+ }
+
+ function getSQL() {
+ global $wgShowSQLErrors;
+ if( !$wgShowSQLErrors ) {
+ return $this->msg( 'sqlhidden', 'SQL hidden' );
+ } else {
+ return $this->sql;
+ }
+ }
+
+ function getLogMessage() {
+ # Don't send to the exception log
+ return false;
}
- /**
- * Table name callback
- * @private
- */
- protected function tableNameCallback( $matches ) {
- return $this->tableName( $matches[1] );
+ 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() ) );
+ }
+ }
}
/**
- * Database abstraction object for mySQL
- * Inherit all methods and properties of Database::Database()
- *
* @addtogroup Database
- * @see Database
*/
-class DatabaseMysql extends Database {
- # Inherit all
-}
+class DBUnexpectedError extends DBError {}
/**
}
}
+class MySQLMasterPos {
+ var $file, $pos;
+ function __construct( $file, $pos ) {
+ $this->file = $file;
+ $this->pos = $pos;
+ }
+
+ function __toString() {
+ return "{$this->file}/{$this->pos}";
+ }
+}