summaryrefslogtreecommitdiff
path: root/includes/db/Database.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db/Database.php')
-rw-r--r--includes/db/Database.php812
1 files changed, 415 insertions, 397 deletions
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 52a59c11..ea5d77da 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -19,7 +19,7 @@ define( 'DEADLOCK_DELAY_MAX', 1500000 );
* Database abstraction object
* @ingroup Database
*/
-class Database {
+abstract class DatabaseBase {
#------------------------------------------------------------------------------
# Variables
@@ -39,6 +39,7 @@ class Database {
protected $mErrorCount = 0;
protected $mLBInfo = array();
protected $mFakeSlaveLag = null, $mFakeMaster = false;
+ protected $mDefaultBigSelects = null;
#------------------------------------------------------------------------------
# Accessors
@@ -49,7 +50,7 @@ class Database {
* Fail function, takes a Database as a parameter
* Set to false for default, 1 for ignore errors
*/
- function failFunction( $function = NULL ) {
+ function failFunction( $function = null ) {
return wfSetVar( $this->mFailFunction, $function );
}
@@ -64,7 +65,7 @@ class Database {
/**
* Boolean, controls output of large amounts of debug information
*/
- function debug( $debug = NULL ) {
+ function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
}
@@ -72,7 +73,7 @@ class Database {
* 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 ) {
+ function bufferResults( $buffer = null ) {
if ( is_null( $buffer ) ) {
return !(bool)( $this->mFlags & DBO_NOBUFFER );
} else {
@@ -87,7 +88,7 @@ class Database {
* code should use lastErrno() and lastError() to handle the
* situation as appropriate.
*/
- function ignoreErrors( $ignoreErrors = NULL ) {
+ function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
}
@@ -95,14 +96,14 @@ class Database {
* The current depth of nested transactions
* @param $level Integer: , default NULL.
*/
- function trxLevel( $level = NULL ) {
+ function trxLevel( $level = null ) {
return wfSetVar( $this->mTrxLevel, $level );
}
/**
* Number of errors logged, only useful when errors are ignored
*/
- function errorCount( $count = NULL ) {
+ function errorCount( $count = null ) {
return wfSetVar( $this->mErrorCount, $count );
}
@@ -113,19 +114,19 @@ class Database {
/**
* Properties passed down from the server info array of the load balancer
*/
- function getLBInfo( $name = NULL ) {
+ 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;
+ return null;
}
}
}
- function setLBInfo( $name, $value = NULL ) {
+ function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
$this->mLBInfo = $name;
} else {
@@ -192,6 +193,14 @@ class Database {
}
/**
+ * Returns true if this database requires that SELECT DISTINCT queries require that all
+ ORDER BY expressions occur in the SELECT list per the SQL92 standard
+ */
+ function standardSelectDistinct() {
+ 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';
*/
@@ -225,14 +234,37 @@ class Database {
*/
function isOpen() { return $this->mOpened; }
+ /**
+ * Set a flag for this connection
+ *
+ * @param $flag Integer: DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_IGNORE: ignore errors (same as ignoreErrors())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+ * and removes it in command line mode
+ * - DBO_PERSISTENT: use persistant database connection
+ */
function setFlag( $flag ) {
$this->mFlags |= $flag;
}
+ /**
+ * Clear a flag for this connection
+ *
+ * @param $flag: same as setFlag()'s $flag param
+ */
function clearFlag( $flag ) {
$this->mFlags &= ~$flag;
}
+ /**
+ * Returns a boolean whether the flag $flag is set for this connection
+ *
+ * @param $flag: same as setFlag()'s $flag param
+ * @return Boolean
+ */
function getFlag( $flag ) {
return !!($this->mFlags & $flag);
}
@@ -252,6 +284,11 @@ class Database {
}
}
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ */
+ abstract function getType();
+
#------------------------------------------------------------------------------
# Other functions
#------------------------------------------------------------------------------
@@ -272,7 +309,7 @@ class Database {
global $wgOut, $wgDBprefix, $wgCommandLineMode;
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
- $wgOut = NULL;
+ $wgOut = null;
}
$this->mFailFunction = $failFunction;
@@ -306,7 +343,7 @@ class Database {
}
/**
- * Same as new Database( ... ), kept for backward compatibility
+ * Same as new DatabaseMysql( ... ), kept for backward compatibility
* @param $server String: database server host
* @param $user String: database user name
* @param $password String: database user password
@@ -316,7 +353,7 @@ class Database {
*/
static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
{
- return new Database( $server, $user, $password, $dbName, $failFunction, $flags );
+ return new DatabaseMysql( $server, $user, $password, $dbName, $failFunction, $flags );
}
/**
@@ -327,114 +364,7 @@ class Database {
* @param $password String: database user password
* @param $dbName String: database name
*/
- function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost;
- wfProfileIn( __METHOD__ );
-
- # 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' ) ) {
- 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;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
-
- $success = false;
-
- wfProfileIn("dbconnect-$server");
-
- # The kernel's default SYN retransmission period is far too slow for us,
- # so we use a short timeout plus a manual retry. Retrying means that a small
- # but finite rate of SYN packet loss won't cause user-visible errors.
- $this->mConn = false;
- if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
- $numAttempts = 2;
- } else {
- $numAttempts = 1;
- }
- $this->installErrorHandler();
- for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
- if ( $i > 1 ) {
- usleep( 1000 );
- }
- if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = mysql_pconnect( $realServer, $user, $password );
- } else {
- # Create a new connection...
- $this->mConn = mysql_connect( $realServer, $user, $password, true );
- }
- if ($this->mConn === false) {
- #$iplus = $i + 1;
- #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
- }
- }
- $phpError = $this->restoreErrorHandler();
- # Always log connection errors
- if ( !$this->mConn ) {
- $error = $this->lastError();
- if ( !$error ) {
- $error = $phpError;
- }
- wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, User: $user, Password: " .
- substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
- $success = false;
- }
-
- wfProfileOut("dbconnect-$server");
-
- if ( $dbName != '' && $this->mConn !== false ) {
- $success = @/**/mysql_select_db( $dbName, $this->mConn );
- if ( !$success ) {
- $error = "Error selecting database $dbName on server {$this->mServer} " .
- "from client host " . wfHostname() . "\n";
- wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
- wfDebug( $error );
- }
- } else {
- # Delay USE query
- $success = (bool)$this->mConn;
- }
-
- if ( $success ) {
- $version = $this->getServerVersion();
- if ( version_compare( $version, '4.1' ) >= 0 ) {
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- global $wgDBmysql5;
- if( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- }
- // Turn off strict mode
- $this->query( "SET sql_mode = ''", __METHOD__ );
- }
-
- // Turn off strict mode if it is on
- } else {
- $this->reportConnectionError( $phpError );
- }
-
- $this->mOpened = $success;
- wfProfileOut( __METHOD__ );
- return $success;
- }
+ abstract function open( $server, $user, $password, $dbName );
protected function installErrorHandler() {
$this->mPHPError = false;
@@ -466,17 +396,9 @@ class Database {
*
* @return Bool operation success. true if already closed.
*/
- function close()
- {
- $this->mOpened = false;
- if ( $this->mConn ) {
- if ( $this->trxLevel() ) {
- $this->immediateCommit();
- }
- return mysql_close( $this->mConn );
- } else {
- return true;
- }
+ function close() {
+ # Stub, should probably be overridden
+ return true;
}
/**
@@ -505,7 +427,7 @@ class Database {
* Should return true if unsure.
*/
function isWriteQuery( $sql ) {
- return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW)\b/i', $sql );
+ return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
}
/**
@@ -629,14 +551,7 @@ class Database {
* @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
* @private
*/
- /*private*/ function doQuery( $sql ) {
- if( $this->bufferResults() ) {
- $ret = mysql_query( $sql, $this->mConn );
- } else {
- $ret = mysql_unbuffered_query( $sql, $this->mConn );
- }
- return $ret;
- }
+ /*private*/ abstract function doQuery( $sql );
/**
* @param $error String
@@ -762,12 +677,8 @@ class Database {
* @param $res Mixed: A SQL result
*/
function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( !@/**/mysql_free_result( $res ) ) {
- throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
- }
+ # Stub. Might not really need to be overridden, since results should
+ # be freed by PHP when the variable goes out of scope anyway.
}
/**
@@ -779,16 +690,7 @@ class Database {
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
- function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- @/**/$row = mysql_fetch_object( $res );
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
+ abstract function fetchObject( $res );
/**
* Fetch the next row from the given result object, in associative array
@@ -798,43 +700,20 @@ class Database {
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
- function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- @/**/$row = mysql_fetch_array( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
+ abstract function fetchRow( $res );
/**
* Get the number of rows in a result object
* @param $res Mixed: A SQL result
*/
- function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- @/**/$n = mysql_num_rows( $res );
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $n;
- }
+ abstract function numRows( $res );
/**
* Get the number of fields in a result object
* See documentation for mysql_num_fields()
* @param $res Mixed: A SQL result
*/
- function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_num_fields( $res );
- }
+ abstract function numFields( $res );
/**
* Get a field name in a result object
@@ -843,12 +722,7 @@ class Database {
* @param $res Mixed: A SQL result
* @param $n Integer
*/
- function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_field_name( $res, $n );
- }
+ abstract function fieldName( $res, $n );
/**
* Get the inserted value of an auto-increment row
@@ -860,7 +734,7 @@ class Database {
* $dbw->insert('page',array('page_id' => $id));
* $id = $dbw->insertId();
*/
- function insertId() { return mysql_insert_id( $this->mConn ); }
+ abstract function insertId();
/**
* Change the position of the cursor in a result object
@@ -868,51 +742,25 @@ class Database {
* @param $res Mixed: A SQL result
* @param $row Mixed: Either MySQL row or ResultWrapper
*/
- function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_data_seek( $res, $row );
- }
+ abstract function dataSeek( $res, $row );
/**
* Get the last error number
* See mysql_errno()
*/
- function lastErrno() {
- if ( $this->mConn ) {
- return mysql_errno( $this->mConn );
- } else {
- return mysql_errno();
- }
- }
+ abstract function lastErrno();
/**
* Get a description of the last error
* See mysql_error() for more details
*/
- function lastError() {
- if ( $this->mConn ) {
- # Even if it's non-zero, it can still be invalid
- wfSuppressWarnings();
- $error = mysql_error( $this->mConn );
- if ( !$error ) {
- $error = mysql_error();
- }
- wfRestoreWarnings();
- } else {
- $error = mysql_error();
- }
- if( $error ) {
- $error .= ' (' . $this->mServer . ')';
- }
- return $error;
- }
+ abstract function lastError();
+
/**
* Get the number of rows affected by the last write query
* See mysql_affected_rows() for more details
*/
- function affectedRows() { return mysql_affected_rows( $this->mConn ); }
+ abstract function affectedRows();
/**
* Simple UPDATE wrapper
@@ -1095,7 +943,7 @@ class Database {
* e.g: selectRow( "page", array( "page_id" ), array( "page_namespace" =>
* NS_MAIN, "page_title" => "Astronomy" ) ) would return an object where
* $obj- >page_id is the ID of the Astronomy article
- * @param $fname String: Calling functio name
+ * @param $fname String: Calling function name
* @param $options Array
* @param $join_conds Array
*
@@ -1118,30 +966,27 @@ class Database {
/**
* Estimate rows in dataset
- * Returns estimated count, based on EXPLAIN output
+ * Returns estimated count - not necessarily an accurate estimate across different databases,
+ * so use sparingly
* Takes same arguments as Database::select()
- */
-
- function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
- $options['EXPLAIN']=true;
- $res = $this->select ($table, $vars, $conds, $fname, $options );
- if ( $res === false )
- return false;
- if (!$this->numRows($res)) {
- $this->freeResult($res);
- return 0;
- }
-
- $rows=1;
-
- while( $plan = $this->fetchObject( $res ) ) {
- $rows *= ($plan->rows > 0)?$plan->rows:1; // avoid resetting to zero
+ *
+ * @param string $table table name
+ * @param array $vars unused
+ * @param array $conds filters on the table
+ * @param string $fname function name for profiling
+ * @param array $options options for select
+ * @return int row count
+ */
+ public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ $rows = 0;
+ $res = $this->select ( $table, 'COUNT(*) AS rowcount', $conds, $fname, $options );
+ if ( $res ) {
+ $row = $this->fetchRow( $res );
+ $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
}
-
- $this->freeResult($res);
- return $rows;
+ $this->freeResult( $res );
+ return $rows;
}
-
/**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
@@ -1178,7 +1023,7 @@ class Database {
$table = $this->tableName( $table );
$res = $this->query( 'DESCRIBE '.$table, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$found = false;
@@ -1200,7 +1045,7 @@ class Database {
function indexExists( $table, $index, $fname = 'Database::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
- return NULL;
+ return null;
} else {
return $info !== false;
}
@@ -1220,7 +1065,7 @@ class Database {
$sql = 'SHOW INDEX FROM '.$table;
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$result = array();
@@ -1257,18 +1102,7 @@ class Database {
* @param $table
* @param $field
*/
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM $table LIMIT 1" );
- $n = mysql_num_fields( $res->result );
- for( $i = 0; $i < $n; $i++ ) {
- $meta = mysql_fetch_field( $res->result, $i );
- if( $field == $meta->name ) {
- return new MySQLField($meta);
- }
- }
- return false;
- }
+ abstract function fieldInfo( $table, $field );
/**
* mysql_field_type() wrapper
@@ -1286,7 +1120,7 @@ class Database {
function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
if ( !$indexInfo ) {
- return NULL;
+ return null;
}
return !$indexInfo[0]->Non_unique;
}
@@ -1440,11 +1274,32 @@ class Database {
}
/**
+ * Bitwise operations
+ */
+
+ function bitNot($field) {
+ return "(~$bitField)";
+ }
+
+ function bitAnd($fieldLeft, $fieldRight) {
+ return "($fieldLeft & $fieldRight)";
+ }
+
+ function bitOr($fieldLeft, $fieldRight) {
+ return "($fieldLeft | $fieldRight)";
+ }
+
+ /**
* Change the current database
+ *
+ * @return bool Success or failure
*/
function selectDB( $db ) {
- $this->mDBname = $db;
- return mysql_select_db( $db, $this->mConn );
+ # Stub. Shouldn't cause serious problems if it's not overridden, but
+ # if your database engine supports a concept similar to MySQL's
+ # databases you may as well. TODO: explain what exactly will fail if
+ # this is not overridden.
+ return true;
}
/**
@@ -1621,9 +1476,7 @@ class Database {
* @param $s String: to be slashed.
* @return String: slashed string.
*/
- function strencode( $s ) {
- return mysql_real_escape_string( $s, $this->mConn );
- }
+ abstract function strencode( $s );
/**
* If it's a string, adds quotes and backslashes
@@ -1642,30 +1495,78 @@ class Database {
}
/**
- * Escape string for safe LIKE usage
+ * Escape string for safe LIKE usage.
+ * WARNING: you should almost never use this function directly,
+ * instead use buildLike() that escapes everything automatically
*/
function escapeLike( $s ) {
- $s=str_replace('\\','\\\\',$s);
- $s=$this->strencode( $s );
- $s=str_replace(array('%','_'),array('\%','\_'),$s);
+ $s = str_replace( '\\', '\\\\', $s );
+ $s = $this->strencode( $s );
+ $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
return $s;
}
/**
+ * LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
+ * containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
+ * Alternatively, the function could be provided with an array of aforementioned parameters.
+ *
+ * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
+ * for subpages of 'My page title'.
+ * Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
+ *
+ * @ return String: fully built LIKE statement
+ */
+ function buildLike() {
+ $params = func_get_args();
+ if (count($params) > 0 && is_array($params[0])) {
+ $params = $params[0];
+ }
+
+ $s = '';
+ foreach( $params as $value) {
+ if( $value instanceof LikeMatch ) {
+ $s .= $value->toString();
+ } else {
+ $s .= $this->escapeLike( $value );
+ }
+ }
+ return " LIKE '" . $s . "' ";
+ }
+
+ /**
+ * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
+ */
+ function anyChar() {
+ return new LikeMatch( '_' );
+ }
+
+ /**
+ * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
+ */
+ function anyString() {
+ return new LikeMatch( '%' );
+ }
+
+ /**
* Returns an appropriately quoted sequence value for inserting a new row.
* MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
* subclass will return an integer, and save the value for insertId()
*/
function nextSequenceValue( $seqName ) {
- return NULL;
+ return null;
}
/**
- * USE INDEX clause
- * PostgreSQL doesn't have them and returns ""
+ * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
+ * is only needed because a) MySQL must be as efficient as possible due to
+ * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
+ * which index to pick. Anyway, other databases might have different
+ * indexes on a given table. So don't bother overriding this unless you're
+ * MySQL.
*/
function useIndexClause( $index ) {
- return "FORCE INDEX (" . $this->indexName( $index ) . ")";
+ return '';
}
/**
@@ -1753,10 +1654,14 @@ class Database {
}
/**
+ * A string to insert into queries to show that they're low-priority, like
+ * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
+ * string and nothing bad should happen.
+ *
* @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
*/
function lowPriorityOption() {
- return 'LOW_PRIORITY';
+ return '';
}
/**
@@ -1810,27 +1715,60 @@ class Database {
}
/**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
+ * Construct a LIMIT query with optional offset. This is used for query
+ * pages. The SQL should be adjusted so that only the first $limit rows
+ * are returned. If $offset is provided as well, then the first $offset
+ * rows should be discarded, and the next $limit rows should be returned.
+ * If the result of the query is not ordered, then the rows to be returned
+ * are theoretically arbitrary.
+ *
+ * $sql is expected to be a SELECT, if that makes a difference. For
+ * UPDATE, limitResultForUpdate should be used.
+ *
+ * The version provided by default works in MySQL and SQLite. It will very
+ * likely need to be overridden for most other DBMSes.
+ *
* @param $sql String: SQL query we will append the limit too
* @param $limit Integer: the SQL limit
* @param $offset Integer the SQL offset (default false)
*/
- function limitResult($sql, $limit, $offset=false) {
- if( !is_numeric($limit) ) {
+ 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 limitResultForUpdate( $sql, $num ) {
+ return $this->limitResult( $sql, $num, 0 );
}
/**
- * Returns an SQL expression for a simple conditional.
- * Uses IF on MySQL.
+ * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
+ * within the UNION construct.
+ * @return Boolean
+ */
+ function unionSupportsOrderAndLimit() {
+ return true; // True for almost every DB supported
+ }
+
+ /**
+ * Construct a UNION query
+ * This is used for providing overload point for other DB abstractions
+ * not compatible with the MySQL syntax.
+ * @param $sqls Array: SQL statements to combine
+ * @param $all Boolean: use UNION ALL
+ * @return String: SQL fragment
+ */
+ function unionQueries($sqls, $all) {
+ $glue = $all ? ') UNION ALL (' : ') UNION (';
+ return '('.implode( $glue, $sqls ) . ')';
+ }
+
+ /**
+ * Returns an SQL expression for a simple conditional. This doesn't need
+ * to be overridden unless CASE isn't supported in your DBMS.
*
* @param $cond String: SQL expression which will result in a boolean value
* @param $trueVal String: SQL expression to return if true
@@ -1838,7 +1776,7 @@ class Database {
* @return String: SQL fragment
*/
function conditional( $cond, $trueVal, $falseVal ) {
- return " IF($cond, $trueVal, $falseVal) ";
+ return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
/**
@@ -1855,17 +1793,27 @@ class Database {
/**
* Determines if the last failure was due to a deadlock
+ * STUB
*/
function wasDeadlock() {
- return $this->lastErrno() == 1213;
+ return false;
}
/**
* Determines if the last query error was something that should be dealt
- * with by pinging the connection and reissuing the query
+ * with by pinging the connection and reissuing the query.
+ * STUB
*/
function wasErrorReissuable() {
- return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+ return false;
+ }
+
+ /**
+ * Determines if the last failure was due to the database being read-only.
+ * STUB
+ */
+ function wasReadOnlyError() {
+ return false;
}
/**
@@ -1935,7 +1883,7 @@ class Database {
# Commit any open transactions
if ( $this->mTrxLevel ) {
- $this->immediateCommit();
+ $this->commit();
}
if ( !is_null( $this->mFakeSlaveLag ) ) {
@@ -2048,6 +1996,21 @@ class Database {
}
/**
+ * Creates a new table with structure copied from existing table
+ * Note that unlike most database abstraction functions, this function does not
+ * automatically append database prefix, because it works at a lower
+ * abstraction level.
+ *
+ * @param $oldName String: name of table whose structure should be copied
+ * @param $newName String: name of table to be created
+ * @param $temporary Boolean: whether the new table should be temporary
+ * @return Boolean: true if operation was successful
+ */
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'Database::duplicateTableStructure' ) {
+ throw new MWException( 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
+ }
+
+ /**
* Return MW-style timestamp used for MySQL schema
*/
function timestamp( $ts=0 ) {
@@ -2089,41 +2052,31 @@ class Database {
}
/**
+ * Returns a wikitext link to the DB's website, e.g.,
+ * return "[http://www.mysql.com/ MySQL]";
+ * Should at least contain plain text, if for some reason
+ * your database has no website.
+ *
* @return String: wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
- return "[http://www.mysql.com/ MySQL]";
- }
+ abstract function getSoftwareLink();
/**
+ * A string describing the current software version, like from
+ * mysql_get_server_info(). Will be listed on Special:Version, etc.
+ *
* @return String: Version information from the database
*/
- function getServerVersion() {
- return mysql_get_server_info( $this->mConn );
- }
+ abstract function getServerVersion();
/**
* Ping the server and try to reconnect if it there is no connection
+ *
+ * @return bool Success or failure
*/
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;
- }
-
- // 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;
- }
- return false;
+ # Stub. Not essential to override.
+ return true;
}
/**
@@ -2135,7 +2088,7 @@ class Database {
wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
return $this->mFakeSlaveLag;
}
- $res = $this->query( 'SHOW PROCESSLIST' );
+ $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
# Find slave SQL thread
while ( $row = $this->fetchObject( $res ) ) {
/* This should work for most situations - when default db
@@ -2149,7 +2102,10 @@ class Database {
$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'
+ $row->State != 'Requesting binlog dump' &&
+ $row->State != 'Waiting to reconnect after a failed master event read' &&
+ $row->State != 'Reconnecting after a failed master event read' &&
+ $row->State != 'Registering slave on master'
) {
# This is it, return the time (except -ve)
if ( $row->Time > 0x7fffffff ) {
@@ -2190,16 +2146,14 @@ class Database {
}
/**
- * 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.
+ * 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. May or may not be necessary for non-MySQL
+ * databases. For most purposes, leaving it as a no-op should be fine.
+ *
* @param $timeout Integer in seconds
*/
- public function setTimeout( $timeout ) {
- $this->query( "SET net_read_timeout=$timeout" );
- $this->query( "SET net_write_timeout=$timeout" );
- }
+ public function setTimeout( $timeout ) {}
/**
* Read and execute SQL commands from a file.
@@ -2211,14 +2165,45 @@ class Database {
function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
$fp = fopen( $filename, 'r' );
if ( false === $fp ) {
- throw new MWException( "Could not open \"{$filename}\".\n" );
+ if (!defined("MEDIAWIKI_INSTALL"))
+ throw new MWException( "Could not open \"{$filename}\".\n" );
+ else
+ return "Could not open \"{$filename}\".\n";
+ }
+ try {
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
}
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
+ catch( MWException $e ) {
+ if ( defined("MEDIAWIKI_INSTALL") ) {
+ $error = $e->getMessage();
+ } else {
+ fclose( $fp );
+ throw $e;
+ }
+ }
+
fclose( $fp );
return $error;
}
/**
+ * Get the full path of a patch file. Originally based on archive()
+ * from updaters.inc. Keep in mind this always returns a patch, as
+ * it fails back to MySQL if no DB-specific patch can be found
+ *
+ * @param $patch String The name of the patch, like patch-something.sql
+ * @return String Full path to patch file
+ */
+ public static function patchPath( $patch ) {
+ global $wgDBtype, $IP;
+ if ( file_exists( "$IP/maintenance/$wgDBtype/archives/$patch" ) ) {
+ return "$IP/maintenance/$wgDBtype/archives/$patch";
+ } else {
+ return "$IP/maintenance/archives/$patch";
+ }
+ }
+
+ /**
* Read and execute commands from an open file handle
* Returns true on success, error string or exception on failure (depending on object's error ignore settings)
* @param $fp String: File handle
@@ -2257,7 +2242,7 @@ class Database {
}
}
- if ( '' != $cmd ) { $cmd .= ' '; }
+ if ( $cmd != '' ) { $cmd .= ' '; }
$cmd .= "$line\n";
if ( $done ) {
@@ -2326,15 +2311,17 @@ class Database {
return $this->indexName( $matches[1] );
}
- /*
+ /**
* Build a concatenation list to feed into a SQL query
- */
+ * @param $stringList Array: list of raw SQL expressions; caller is responsible for any quoting
+ * @return String
+ */
function buildConcat( $stringList ) {
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
/**
- * Acquire a lock
+ * Acquire a named lock
*
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
@@ -2343,32 +2330,44 @@ class Database {
* @param $method String: Name of method calling us
* @return bool
*/
- public function lock( $lockName, $method ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT GET_LOCK($lockName, 5) AS lockstatus", $method );
- $row = $this->fetchObject( $result );
- $this->freeResult( $result );
-
- if( $row->lockstatus == 1 ) {
- return true;
- } else {
- wfDebug( __METHOD__." failed to acquire lock\n" );
- return false;
- }
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ return true;
}
+
/**
* Release a lock.
*
- * @todo fixme - Figure out a way to return a bool
- * based on successful lock release.
- *
* @param $lockName String: Name of lock to release
* @param $method String: Name of method calling us
+ *
+ * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ * @return Returns 1 if the lock was released, 0 if the lock was not established
+ * by this thread (in which case the lock is not released), and NULL if the named
+ * lock did not exist
*/
public function unlock( $lockName, $method ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT RELEASE_LOCK($lockName)", $method );
- $this->freeResult( $result );
+ return true;
+ }
+
+ /**
+ * Lock specific tables
+ *
+ * @param $read Array of tables to lock for read access
+ * @param $write Array of tables to lock for write access
+ * @param $method String name of caller
+ * @param $lowPriority bool Whether to indicate writes to be LOW PRIORITY
+ */
+ public function lockTables( $read, $write, $method, $lowPriority = true ) {
+ return true;
+ }
+
+ /**
+ * Unlock specific tables
+ *
+ * @param $method String the caller
+ */
+ public function unlockTables( $method ) {
+ return true;
}
/**
@@ -2380,19 +2379,21 @@ class Database {
public function getSearchEngine() {
return "SearchMySQL";
}
-}
-/**
- * Database abstraction object for mySQL
- * Inherit all methods and properties of Database::Database()
- *
- * @ingroup Database
- * @see Database
- */
-class DatabaseMysql extends Database {
- # Inherit all
+ /**
+ * Allow or deny "big selects" for this session only. This is done by setting
+ * the sql_big_selects session variable.
+ *
+ * This is a MySQL-specific feature.
+ *
+ * @param mixed $value true for allow, false for deny, or "default" to restore the initial value
+ */
+ public function setBigSelects( $value = true ) {
+ // no-op
+ }
}
+
/******************************************************************************
* Utility classes
*****************************************************************************/
@@ -2502,10 +2503,19 @@ class DBError extends MWException {
* @param $db Database object which threw the error
* @param $error A simple error message to be used for debugging
*/
- function __construct( Database &$db, $error ) {
+ function __construct( DatabaseBase &$db, $error ) {
$this->db =& $db;
parent::__construct( $error );
}
+
+ function getText() {
+ global $wgShowDBErrorBacktrace;
+ $s = $this->getMessage() . "\n";
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
+ }
+ return $s;
+ }
}
/**
@@ -2514,7 +2524,7 @@ class DBError extends MWException {
class DBConnectionError extends DBError {
public $error;
- function __construct( Database &$db, $error = 'unknown error' ) {
+ function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
$msg = 'DB connection error';
if ( trim( $error ) != '' ) {
$msg .= ": $error";
@@ -2533,10 +2543,6 @@ class DBConnectionError extends DBError {
return false;
}
- function getText() {
- return $this->getMessage() . "\n";
- }
-
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -2553,7 +2559,7 @@ class DBConnectionError extends DBError {
}
function getHTML() {
- global $wgLang, $wgMessageCache, $wgUseFileCache;
+ global $wgLang, $wgMessageCache, $wgUseFileCache, $wgShowDBErrorBacktrace;
$sorry = 'Sorry! This site is experiencing technical difficulties.';
$again = 'Try waiting a few minutes and reloading.';
@@ -2577,30 +2583,31 @@ class DBConnectionError extends DBError {
$noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
$text = str_replace( '$1', $this->error, $noconnect );
- /*
- if ( $GLOBALS['wgShowExceptionDetails'] ) {
- $text .= '</p><p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
- "</p>\n";
- }*/
+ if ( $wgShowDBErrorBacktrace ) {
+ $text .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ }
$extra = $this->searchForm();
if( $wgUseFileCache ) {
- $cache = $this->fileCachedPage();
- # Cached version on file system?
- if( $cache !== null ) {
- # Hack: extend the body for error messages
- $cache = str_replace( array('</html>','</body>'), '', $cache );
- # Add cache notice...
- $cachederror = "This is a cached copy of the requested page, and may not be up to date. ";
- # Localize it if possible...
- if( $wgLang instanceof Language ) {
- $cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
+ try {
+ $cache = $this->fileCachedPage();
+ # Cached version on file system?
+ if( $cache !== null ) {
+ # Hack: extend the body for error messages
+ $cache = str_replace( array('</html>','</body>'), '', $cache );
+ # Add cache notice...
+ $cachederror = "This is a cached copy of the requested page, and may not be up to date. ";
+ # Localize it if possible...
+ if( $wgLang instanceof Language ) {
+ $cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
+ }
+ $warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>";
+ # Output cached page with notices on bottom and re-close body
+ return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>";
}
- $warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>";
- # Output cached page with notices on bottom and re-close body
- return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>";
+ } catch( MWException $e ) {
+ // Do nothing, just use the default page
}
}
# Headers needed here - output is just the error message
@@ -2631,8 +2638,6 @@ class DBConnectionError extends DBError {
<input type="hidden" name="ie" value="$wgInputEncoding" />
<input type="hidden" name="oe" value="$wgInputEncoding" />
- <img src="http://www.google.com/logos/Logo_40wht.gif" alt="" style="float:left; margin-left: 1.5em; margin-right: 1.5em;" />
-
<input type="text" name="q" size="31" maxlength="255" value="$search" />
<input type="submit" name="btnG" value="$googlesearch" />
<div>
@@ -2653,9 +2658,9 @@ EOT;
$mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
}
- if($wgTitle) {
+ if( $wgTitle ) {
$t =& $wgTitle;
- } elseif($title) {
+ } elseif( $title ) {
$t = Title::newFromURL( $title );
} else {
$t = Title::newFromText( $mainpage );
@@ -2681,7 +2686,7 @@ EOT;
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
- function __construct( Database &$db, $error, $errno, $sql, $fname ) {
+ function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
$message = "A database error has occurred\n" .
"Query: $sql\n" .
"Function: $fname\n" .
@@ -2695,11 +2700,16 @@ class DBQueryError extends DBError {
}
function getText() {
+ global $wgShowDBErrorBacktrace;
if ( $this->useMessageCache() ) {
- return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
- htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
+ $s = wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
+ htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
+ }
+ return $s;
} else {
- return $this->getMessage();
+ return parent::getText();
}
}
@@ -2722,12 +2732,17 @@ class DBQueryError extends DBError {
}
function getHTML() {
+ global $wgShowDBErrorBacktrace;
if ( $this->useMessageCache() ) {
- return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
+ $s = wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
} else {
- return nl2br( htmlspecialchars( $this->getMessage() ) );
+ $s = nl2br( htmlspecialchars( $this->getMessage() ) );
}
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ }
+ return $s;
}
}
@@ -2841,15 +2856,18 @@ class ResultWrapper implements Iterator {
}
}
-class MySQLMasterPos {
- var $file, $pos;
+/**
+ * Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
+ * and thus need no escaping. Don't instantiate it manually, use Database::anyChar() and anyString() instead.
+ */
+class LikeMatch {
+ private $str;
- function __construct( $file, $pos ) {
- $this->file = $file;
- $this->pos = $pos;
+ public function __construct( $s ) {
+ $this->str = $s;
}
- function __toString() {
- return "{$this->file}/{$this->pos}";
+ public function toString() {
+ return $this->str;
}
}