2004-02-18 02:15:00 +00:00
< ? php
2004-09-02 23:28:24 +00:00
/**
2005-08-02 13:35:19 +00:00
* This file deals with MySQL interface functions
2004-09-02 23:28:24 +00:00
* and query specifics / optimisations
*/
2004-09-03 16:36:46 +00:00
/** Number of times to re-try an operation in case of deadlock */
2004-08-22 17:24:50 +00:00
define ( 'DEADLOCK_TRIES' , 4 );
2004-09-03 16:36:46 +00:00
/** Minimum time to wait before retry, in microseconds */
2004-08-22 17:24:50 +00:00
define ( 'DEADLOCK_DELAY_MIN' , 500000 );
2004-09-03 16:36:46 +00:00
/** Maximum time to wait before retry */
2004-08-22 17:24:50 +00:00
define ( 'DEADLOCK_DELAY_MAX' , 1500000 );
2004-07-18 08:48:43 +00:00
2007-04-04 05:22:37 +00:00
/**
2008-03-30 09:48:15 +00:00
* Database abstraction object
2007-04-20 08:55:14 +00:00
* @ addtogroup Database
2007-04-04 05:22:37 +00:00
*/
2008-03-30 09:48:15 +00:00
class Database {
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
#------------------------------------------------------------------------------
# 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 ();
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 );
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Output page , used for reporting errors
* FALSE means discard output
*/
function setOutputPage ( $out ) {
$this -> mOut = $out ;
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Boolean , controls output of large amounts of debug information
*/
function debug ( $debug = NULL ) {
return wfSetBit ( $this -> mFlags , DBO_DEBUG , $debug );
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 );
}
2007-09-23 19:54:56 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 );
2007-09-23 19:54:56 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* The current depth of nested transactions
* @ param $level Integer : , default NULL .
*/
function trxLevel ( $level = NULL ) {
return wfSetVar ( $this -> mTrxLevel , $level );
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Number of errors logged , only useful when errors are ignored
*/
function errorCount ( $count = NULL ) {
return wfSetVar ( $this -> mErrorCount , $count );
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
function tablePrefix ( $prefix = null ) {
return wfSetVar ( $this -> mTablePrefix , $prefix );
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 ;
}
}
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
function setLBInfo ( $name , $value = NULL ) {
if ( is_null ( $value ) ) {
$this -> mLBInfo = $name ;
} else {
$this -> mLBInfo [ $name ] = $value ;
}
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Set lag time in seconds for a fake slave
*/
function setFakeSlaveLag ( $lag ) {
$this -> mFakeSlaveLag = $lag ;
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Make this connection a fake master
*/
function setFakeMaster ( $enabled = true ) {
$this -> mFakeMaster = $enabled ;
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Returns true if this database supports ( and uses ) cascading deletes
*/
function cascadingDeletes () {
return false ;
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Returns true if this database supports ( and uses ) triggers ( e . g . on the page table )
*/
function cleanupTriggers () {
return false ;
2007-03-19 02:40:32 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 ;
}
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
/**
* Returns true if this database uses timestamps rather than integers
*/
function realTimestamps () {
return false ;
}
2006-06-06 23:07:26 +00:00
/**
2008-03-30 09:48:15 +00:00
* Returns true if this database does an implicit sort when doing GROUP BY
2006-06-06 23:07:26 +00:00
*/
2008-03-30 09:48:15 +00:00
function implicitGroupby () {
return true ;
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 ;
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 () {
2006-06-06 23:07:26 +00:00
return false ;
}
2008-03-30 09:48:15 +00:00
/**
* Returns true if this database can use functional indexes
*/
function functionalIndexes () {
2006-06-06 23:07:26 +00:00
return false ;
}
2008-03-30 09:48:15 +00:00
/** #@+
* Get function
*/
function lastQuery () { return $this -> mLastQuery ; }
function isOpen () { return $this -> mOpened ; }
/**#@-*/
function setFlag ( $flag ) {
$this -> mFlags |= $flag ;
2006-08-02 17:40:09 +00:00
}
2008-03-30 09:48:15 +00:00
function clearFlag ( $flag ) {
$this -> mFlags &= ~ $flag ;
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
function getFlag ( $flag ) {
return !! ( $this -> mFlags & $flag );
}
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
/**
* General read - only accessor
*/
function getProperty ( $name ) {
return $this -> $name ;
}
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
#------------------------------------------------------------------------------
# Other functions
#------------------------------------------------------------------------------
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
/**@ {{
* Constructor .
* @ param string $server database server host
* @ param string $user database user name
* @ param string $password database user password
* @ param string $dbname database name
* @ param failFunction
* @ param $flags
* @ param $tablePrefix String : database table prefixes . By default use the prefix gave in LocalSettings . php
*/
function __construct ( $server = false , $user = false , $password = false , $dbName = false ,
$failFunction = false , $flags = 0 , $tablePrefix = 'get from global' ) {
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
global $wgOut , $wgDBprefix , $wgCommandLineMode ;
# Can't get a reference if it hasn't been set yet
if ( ! isset ( $wgOut ) ) {
$wgOut = NULL ;
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
$this -> mOut =& $wgOut ;
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
$this -> mFailFunction = $failFunction ;
$this -> mFlags = $flags ;
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
if ( $this -> mFlags & DBO_DEFAULT ) {
if ( $wgCommandLineMode ) {
$this -> mFlags &= ~ DBO_TRX ;
2006-06-06 23:07:26 +00:00
} else {
2008-03-30 09:48:15 +00:00
$this -> mFlags |= DBO_TRX ;
2006-06-06 23:07:26 +00:00
}
}
2008-03-30 09:48:15 +00:00
/*
// Faster read-only access
if ( wfReadOnly () ) {
$this -> mFlags |= DBO_PERSISTENT ;
$this -> mFlags &= ~ DBO_TRX ;
} */
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
/** Get the default table prefix*/
if ( $tablePrefix == 'get from global' ) {
$this -> mTablePrefix = $wgDBprefix ;
2006-06-06 23:07:26 +00:00
} else {
2008-03-30 09:48:15 +00:00
$this -> mTablePrefix = $tablePrefix ;
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
if ( $server ) {
$this -> open ( $server , $user , $password , $dbName );
2006-06-06 23:07:26 +00:00
}
}
2006-08-02 17:40:09 +00:00
2008-03-30 09:48:15 +00:00
/**
* @ static
* @ param failFunction
* @ param $flags
*/
static function newFromParams ( $server , $user , $password , $dbName , $failFunction = false , $flags = 0 )
{
return new Database ( $server , $user , $password , $dbName , $failFunction , $flags );
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Usually aborts on failure
* If the failFunction is set to a non - zero integer , returns success
*/
function open ( $server , $user , $password , $dbName ) {
2008-04-05 18:11:23 +00:00
global $wguname , $wgAllDBsAreLocalhost ;
2008-03-30 09:48:15 +00:00
wfProfileIn ( __METHOD__ );
# Test for missing mysql.so
# First try to load it
if ( !@ extension_loaded ( 'mysql' )) {
@ dl ( 'mysql.so' );
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
# 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 " );
}
2006-06-06 23:07:26 +00:00
2008-04-05 18:11:23 +00:00
# Debugging hack -- fake cluster
if ( $wgAllDBsAreLocalhost ) {
$realServer = 'localhost' ;
} else {
$realServer = $server ;
}
2008-03-30 09:48:15 +00:00
$this -> close ();
$this -> mServer = $server ;
$this -> mUser = $user ;
$this -> mPassword = $password ;
$this -> mDBname = $dbName ;
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
$success = false ;
2004-01-10 16:44:31 +00:00
2008-03-30 09:48:15 +00:00
wfProfileIn ( " dbconnect- $server " );
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
# 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 ++ ) {
if ( $i > 1 ) {
usleep ( 1000 );
}
if ( $this -> mFlags & DBO_PERSISTENT ) {
2008-04-05 18:11:23 +00:00
@/**/ $this -> mConn = mysql_pconnect ( $realServer , $user , $password );
2008-03-30 09:48:15 +00:00
} else {
# Create a new connection...
2008-04-05 18:11:23 +00:00
@/**/ $this -> mConn = mysql_connect ( $realServer , $user , $password , true );
2008-03-30 09:48:15 +00:00
}
if ( $this -> mConn === false ) {
#$iplus = $i + 1;
#wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
}
}
wfProfileOut ( " dbconnect- $server " );
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
if ( $dbName != '' ) {
if ( $this -> mConn !== false ) {
$success = @/**/ mysql_select_db ( $dbName , $this -> mConn );
if ( ! $success ) {
$error = " Error selecting database $dbName on server { $this -> mServer } " .
" from client host { $wguname [ 'nodename' ] } \n " ;
wfLogDBError ( " Error selecting database $dbName on server { $this -> mServer } \n " );
wfDebug ( $error );
}
} else {
wfDebug ( " DB connection error \n " );
wfDebug ( " Server: $server , User: $user , Password: " .
substr ( $password , 0 , 3 ) . " ..., error: " . mysql_error () . " \n " );
$success = false ;
}
} else {
# Delay USE query
$success = ( bool ) $this -> mConn ;
}
2006-06-06 23:07:26 +00:00
2008-03-30 09:48:15 +00:00
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__ );
}
2004-01-10 16:44:31 +00:00
2008-03-30 09:48:15 +00:00
// Turn off strict mode if it is on
} else {
$this -> reportConnectionError ();
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
$this -> mOpened = $success ;
wfProfileOut ( __METHOD__ );
return $success ;
2004-07-24 07:24:04 +00:00
}
2008-03-30 09:48:15 +00:00
/**@}}*/
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Closes a database connection .
* if it is open : commits any open transactions
*
* @ return bool operation success . true if already closed .
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function close ()
{
$this -> mOpened = false ;
if ( $this -> mConn ) {
if ( $this -> trxLevel () ) {
$this -> immediateCommit ();
}
return mysql_close ( $this -> mConn );
} else {
return true ;
}
2004-07-24 07:24:04 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* @ param string $error fallback error message , used if none is given by MySQL
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function reportConnectionError ( $error = 'Unknown error' ) {
$myError = $this -> lastError ();
if ( $myError ) {
$error = $myError ;
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
if ( $this -> mFailFunction ) {
# Legacy error handling method
if ( ! is_int ( $this -> mFailFunction ) ) {
$ff = $this -> mFailFunction ;
$ff ( $this , $error );
}
2004-07-24 07:24:04 +00:00
} else {
2008-03-30 09:48:15 +00:00
# New method
wfLogDBError ( " Connection error: $error\n " );
throw new DBConnectionError ( $this , $error );
2004-07-24 07:24:04 +00:00
}
}
2004-01-10 16:44:31 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Usually aborts on failure . If errors are explicitly ignored , returns success .
*
* @ param $sql String : SQL query
* @ param $fname String : Name of the calling function , for profiling / SHOW PROCESSLIST
* comment ( you can use __METHOD__ or add some extra info )
* @ param $tempIgnore Bool : Whether to avoid throwing an exception on errors ...
* maybe best to catch the exception instead ?
* @ return true for a successful write query , ResultWrapper object for a successful read query ,
* or false on failure if $tempIgnore set
* @ throws DBQueryError Thrown when the database returns an error of any kind
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
public function query ( $sql , $fname = '' , $tempIgnore = false ) {
global $wgProfiling ;
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
$isMaster = ! is_null ( $this -> getLBInfo ( 'master' ) );
if ( $wgProfiling ) {
# generalizeSQL will probably cut down the query to reasonable
# logging size most of the time. The substr is really just a sanity check.
2004-07-24 07:24:04 +00:00
2008-03-30 09:48:15 +00:00
# Who's been wasting my precious column space? -- TS
#$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
2005-04-24 08:31:12 +00:00
2008-03-30 09:48:15 +00:00
if ( $isMaster ) {
$queryProf = 'query-m: ' . substr ( Database :: generalizeSQL ( $sql ), 0 , 255 );
$totalProf = 'Database::query-master' ;
2005-10-29 01:41:36 +00:00
} else {
2008-03-30 09:48:15 +00:00
$queryProf = 'query: ' . substr ( Database :: generalizeSQL ( $sql ), 0 , 255 );
$totalProf = 'Database::query' ;
2005-10-29 01:41:36 +00:00
}
2008-03-30 09:48:15 +00:00
wfProfileIn ( $totalProf );
wfProfileIn ( $queryProf );
2005-10-29 01:41:36 +00:00
}
2008-03-30 09:48:15 +00:00
$this -> mLastQuery = $sql ;
# Add a comment for easy SHOW PROCESSLIST interpretation
#if ( $fname ) {
global $wgUser ;
if ( is_object ( $wgUser ) && ! ( $wgUser instanceof StubObject ) ) {
$userName = $wgUser -> getName ();
if ( mb_strlen ( $userName ) > 15 ) {
$userName = mb_substr ( $userName , 0 , 15 ) . '...' ;
}
$userName = str_replace ( '/' , '' , $userName );
} else {
$userName = '' ;
}
$commentedSql = preg_replace ( '/\s/' , " /* $fname $userName */ " , $sql , 1 );
#} else {
# $commentedSql = $sql;
#}
# If DBO_TRX is set, start a transaction
if ( ( $this -> mFlags & DBO_TRX ) && ! $this -> trxLevel () &&
$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 ();
2005-10-29 01:41:36 +00:00
}
2008-03-30 09:48:15 +00:00
if ( $this -> debug () ) {
$sqlx = substr ( $commentedSql , 0 , 500 );
$sqlx = strtr ( $sqlx , " \t \n " , ' ' );
if ( $isMaster ) {
wfDebug ( " SQL-master: $sqlx\n " );
} else {
wfDebug ( " SQL: $sqlx\n " );
}
}
# Do the query and handle errors
$ret = $this -> doQuery ( $commentedSql );
# Try reconnecting if the connection was lost
if ( false === $ret && ( $this -> lastErrno () == 2013 || $this -> lastErrno () == 2006 ) ) {
# Transaction is gone, like it or not
$this -> mTrxLevel = 0 ;
wfDebug ( " Connection lost, reconnecting... \n " );
if ( $this -> ping () ) {
wfDebug ( " Reconnected \n " );
$sqlx = substr ( $commentedSql , 0 , 500 );
$sqlx = strtr ( $sqlx , " \t \n " , ' ' );
global $wgRequestTime ;
$elapsed = round ( microtime ( true ) - $wgRequestTime , 3 );
wfLogDBError ( " Connection lost and reconnected after { $elapsed } s, query: $sqlx\n " );
$ret = $this -> doQuery ( $commentedSql );
} else {
wfDebug ( " Failed \n " );
}
}
if ( false === $ret ) {
$this -> reportQueryError ( $this -> lastError (), $this -> lastErrno (), $sql , $fname , $tempIgnore );
}
if ( $wgProfiling ) {
wfProfileOut ( $queryProf );
wfProfileOut ( $totalProf );
}
return $this -> resultObject ( $ret );
2006-07-17 00:54:40 +00:00
}
/**
2008-03-30 09:48:15 +00:00
* The DBMS - dependent part of query ()
* @ param $sql String : SQL query .
* @ return Result object to feed to fetchObject , fetchRow , ... ; or false on failure
* @ access private
2006-07-17 00:54:40 +00:00
*/
2008-03-30 09:48:15 +00:00
/*private*/ function doQuery ( $sql ) {
if ( $this -> bufferResults () ) {
$ret = mysql_query ( $sql , $this -> mConn );
} else {
$ret = mysql_unbuffered_query ( $sql , $this -> mConn );
}
return $ret ;
2006-07-17 00:54:40 +00:00
}
2006-08-16 00:58:42 +00:00
/**
2008-03-30 09:48:15 +00:00
* @ param $error
* @ param $errno
* @ param $sql
* @ param string $fname
* @ param bool $tempIgnore
2006-08-16 00:58:42 +00:00
*/
2008-03-30 09:48:15 +00:00
function reportQueryError ( $error , $errno , $sql , $fname , $tempIgnore = false ) {
global $wgCommandLineMode ;
# Ignore errors during error handling to avoid infinite recursion
$ignore = $this -> ignoreErrors ( true );
++ $this -> mErrorCount ;
if ( $ignore || $tempIgnore ) {
wfDebug ( " SQL ERROR (ignored): $error\n " );
$this -> ignoreErrors ( $ignore );
} else {
$sql1line = str_replace ( " \n " , " \\ n " , $sql );
wfLogDBError ( " $fname\t { $this -> mServer } \t $errno\t $error\t $sql1line\n " );
wfDebug ( " SQL ERROR: " . $error . " \n " );
throw new DBQueryError ( $this , $error , $errno , $sql , $fname );
}
2006-08-16 00:58:42 +00:00
}
2008-03-30 09:48:15 +00:00
2006-11-28 21:40:42 +00:00
/**
2008-03-30 09:48:15 +00:00
* Intended to be compatible with the PEAR :: DB wrapper functions .
* http :// pear . php . net / manual / en / package . database . db . intro - execute . php
*
* ? = scalar value , quoted as necessary
* ! = raw SQL bit ( a function for instance )
* & = filename ; reads the file and inserts as a blob
* ( we don ' t use this though ... )
*/
function prepare ( $sql , $func = 'Database::prepare' ) {
/* MySQL doesn ' t support prepared statements ( yet ), so just
pack up the query for reference . We ' ll manually replace
the bits later . */
return array ( 'query' => $sql , 'func' => $func );
}
function freePrepared ( $prepared ) {
/* No-op for MySQL */
2006-11-28 21:40:42 +00:00
}
2007-01-02 21:34:42 +00:00
/**
2008-03-30 09:48:15 +00:00
* Execute a prepared query with the various arguments
* @ param string $prepared the prepared sql
* @ param mixed $args Either an array here , or put scalars as varargs
2007-01-02 21:34:42 +00:00
*/
2008-03-30 09:48:15 +00:00
function execute ( $prepared , $args = null ) {
if ( ! is_array ( $args ) ) {
# Pull the var args
$args = func_get_args ();
array_shift ( $args );
}
$sql = $this -> fillPrepared ( $prepared [ 'query' ], $args );
return $this -> query ( $sql , $prepared [ 'func' ] );
2007-01-02 21:34:42 +00:00
}
2007-09-02 18:03:10 +00:00
/**
2008-03-30 09:48:15 +00:00
* Prepare & execute an SQL statement , quoting and inserting arguments
* in the appropriate places .
* @ param string $query
* @ param string $args ...
2007-09-02 18:03:10 +00:00
*/
2008-03-30 09:48:15 +00:00
function safeQuery ( $query , $args = null ) {
$prepared = $this -> prepare ( $query , 'Database::safeQuery' );
if ( ! is_array ( $args ) ) {
# Pull the var args
$args = func_get_args ();
array_shift ( $args );
}
$retval = $this -> execute ( $prepared , $args );
$this -> freePrepared ( $prepared );
return $retval ;
2007-09-02 18:03:10 +00:00
}
2007-01-23 14:47:12 +00:00
/**
2008-03-30 09:48:15 +00:00
* For faking prepared SQL statements on DBs that don ' t support
* it directly .
* @ param string $preparedSql - a 'preparable' SQL statement
* @ param array $args - array of arguments to fill it with
* @ return string executable SQL
2007-01-23 14:47:12 +00:00
*/
2008-03-30 09:48:15 +00:00
function fillPrepared ( $preparedQuery , $args ) {
reset ( $args );
$this -> preparedArgs =& $args ;
return preg_replace_callback ( '/(\\\\[?!&]|[?!&])/' ,
array ( & $this , 'fillPreparedArg' ), $preparedQuery );
2007-01-23 14:47:12 +00:00
}
2007-07-30 14:10:42 +00:00
/**
2008-03-30 09:48:15 +00:00
* preg_callback func for fillPrepared ()
* The arguments should be in $this -> preparedArgs and must not be touched
* while we ' re doing this .
*
* @ param array $matches
* @ return string
* @ private
2007-07-30 14:10:42 +00:00
*/
2008-03-30 09:48:15 +00:00
function fillPreparedArg ( $matches ) {
switch ( $matches [ 1 ] ) {
case '\\?' : return '?' ;
case '\\!' : return '!' ;
case '\\&' : return '&' ;
}
list ( /* $n */ , $arg ) = each ( $this -> preparedArgs );
switch ( $matches [ 1 ] ) {
case '?' : return $this -> addQuotes ( $arg );
case '!' : return $arg ;
case '&' :
# return $this->addQuotes( file_get_contents( $arg ) );
throw new DBUnexpectedError ( $this , '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
default :
throw new DBUnexpectedError ( $this , 'Received invalid match. This should never happen!' );
}
2007-07-30 14:10:42 +00:00
}
2004-09-03 16:36:46 +00:00
/** #@+
2008-03-30 09:48:15 +00:00
* @ param mixed $res A SQL result
2004-09-03 16:36:46 +00:00
*/
2006-06-06 23:07:26 +00:00
/**
2008-03-30 09:48:15 +00:00
* Free a result object
2006-06-06 23:07:26 +00:00
*/
2008-03-30 09:48:15 +00:00
function freeResult ( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
}
if ( !@/**/ mysql_free_result ( $res ) ) {
throw new DBUnexpectedError ( $this , " Unable to free MySQL result " );
}
2006-06-06 23:07:26 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* Fetch the next row from the given result object , in object form .
* Fields can be retrieved with $row -> fieldname , with fields acting like
* member variables .
*
* @ param $res SQL result object as returned from Database :: query (), etc .
* @ return MySQL row object
* @ throws DBUnexpectedError Thrown if the database returns an error
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function fetchObject ( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
2004-03-23 10:13:59 +00:00
}
2008-03-30 09:48:15 +00:00
@/**/ $row = mysql_fetch_object ( $res );
if ( $this -> lastErrno () ) {
throw new DBUnexpectedError ( $this , 'Error in fetchObject(): ' . htmlspecialchars ( $this -> lastError () ) );
2004-07-24 07:24:04 +00:00
}
2008-03-30 09:48:15 +00:00
return $row ;
}
2005-04-12 04:03:21 +00:00
2008-03-30 09:48:15 +00:00
/**
* Fetch the next row from the given result object , in associative array
* form . Fields are retrieved with $row [ 'fieldname' ] .
*
* @ param $res SQL result object as returned from Database :: query (), etc .
* @ return MySQL row object
* @ throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchRow ( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
@/**/ $row = mysql_fetch_array ( $res );
if ( $this -> lastErrno () ) {
throw new DBUnexpectedError ( $this , 'Error in fetchRow(): ' . htmlspecialchars ( $this -> lastError () ) );
2004-07-10 03:09:26 +00:00
}
2008-03-30 09:48:15 +00:00
return $row ;
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get the number of rows in a result object
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 ;
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get the number of fields in a result object
* See documentation for mysql_num_fields ()
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function numFields ( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
2004-11-09 15:25:40 +00:00
}
2008-03-30 09:48:15 +00:00
return mysql_num_fields ( $res );
}
2004-11-09 15:25:40 +00:00
2008-03-30 09:48:15 +00:00
/**
* Get a field name in a result object
* See documentation for mysql_field_name () :
* http :// www . php . net / mysql_field_name
*/
function fieldName ( $res , $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
2004-06-25 04:32:45 +00:00
}
2008-03-30 09:48:15 +00:00
return mysql_field_name ( $res , $n );
}
/**
* Get the inserted value of an auto - increment row
*
* The value inserted should be fetched from nextSequenceValue ()
*
* Example :
* $id = $dbw -> nextSequenceValue ( 'page_page_id_seq' );
* $dbw -> insert ( 'page' , array ( 'page_id' => $id ));
* $id = $dbw -> insertId ();
*/
function insertId () { return mysql_insert_id ( $this -> mConn ); }
/**
* Change the position of the cursor in a result object
* See mysql_data_seek ()
*/
function dataSeek ( $res , $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
2005-04-12 04:03:21 +00:00
}
2008-03-30 09:48:15 +00:00
return mysql_data_seek ( $res , $row );
}
2005-04-12 04:03:21 +00:00
2008-03-30 09:48:15 +00:00
/**
* Get the last error number
* See mysql_errno ()
*/
function lastErrno () {
if ( $this -> mConn ) {
return mysql_errno ( $this -> mConn );
2004-01-10 16:44:31 +00:00
} else {
2008-03-30 09:48:15 +00:00
return mysql_errno ();
2004-01-10 16:44:31 +00:00
}
2008-03-30 09:48:15 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
/**
* 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 ();
2006-09-13 15:27:04 +00:00
}
2008-03-30 09:48:15 +00:00
wfRestoreWarnings ();
2006-09-13 15:27:04 +00:00
} else {
2008-03-30 09:48:15 +00:00
$error = mysql_error ();
2004-01-10 16:44:31 +00:00
}
2008-03-30 09:48:15 +00:00
if ( $error ) {
$error .= ' (' . $this -> mServer . ')' ;
}
return $error ;
2004-01-10 16:44:31 +00:00
}
2008-03-30 09:48:15 +00:00
/**
* 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 ); }
/**#@-*/ // end of template : @param $result
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Simple UPDATE wrapper
* Usually aborts on failure
* If errors are explicitly ignored , returns success
2004-09-03 16:36:46 +00:00
*
2008-03-30 09:48:15 +00:00
* This function exists for historical reasons , Database :: update () has a more standard
* calling convention and feature set
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function set ( $table , $var , $value , $cond , $fname = 'Database::set' )
2004-01-10 16:44:31 +00:00
{
2008-03-30 09:48:15 +00:00
$table = $this -> tableName ( $table );
$sql = " UPDATE $table SET $var = ' " .
$this -> strencode ( $value ) . " ' WHERE ( $cond ) " ;
return ( bool ) $this -> query ( $sql , $fname );
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Simple SELECT wrapper , returns a single field , input must be encoded
* Usually aborts on failure
* If errors are explicitly ignored , returns FALSE on failure
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function selectField ( $table , $var , $cond = '' , $fname = 'Database::selectField' , $options = array () ) {
if ( ! is_array ( $options ) ) {
$options = array ( $options );
2005-10-28 01:08:49 +00:00
}
2008-03-30 09:48:15 +00:00
$options [ 'LIMIT' ] = 1 ;
2006-01-07 13:31:29 +00:00
2008-03-30 09:48:15 +00:00
$res = $this -> select ( $table , $var , $cond , $fname , $options );
if ( $res === false || ! $this -> numRows ( $res ) ) {
return false ;
}
$row = $this -> fetchRow ( $res );
if ( $row !== false ) {
$this -> freeResult ( $res );
return $row [ 0 ];
2004-01-10 16:44:31 +00:00
} else {
2008-03-30 09:48:15 +00:00
return false ;
2004-01-10 16:44:31 +00:00
}
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Returns an optional USE INDEX clause to go after the table , and a
* string to go at the end of the query
2007-04-19 02:01:19 +00:00
*
2008-03-30 09:48:15 +00:00
* @ private
*
* @ param array $options an associative array of options to be turned into
* an SQL query , valid keys are listed in the function .
* @ return array
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function makeSelectOptions ( $options ) {
$preLimitTail = $postLimitTail = '' ;
$startOpts = '' ;
2005-10-22 20:52:30 +00:00
2008-03-30 09:48:15 +00:00
$noKeyOptions = array ();
foreach ( $options as $key => $option ) {
if ( is_numeric ( $key ) ) {
$noKeyOptions [ $option ] = true ;
2006-03-28 05:11:40 +00:00
}
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
if ( isset ( $options [ 'GROUP BY' ] ) ) $preLimitTail .= " GROUP BY { $options [ 'GROUP BY' ] } " ;
if ( isset ( $options [ 'HAVING' ] ) ) $preLimitTail .= " HAVING { $options [ 'HAVING' ] } " ;
if ( isset ( $options [ 'ORDER BY' ] ) ) $preLimitTail .= " ORDER BY { $options [ 'ORDER BY' ] } " ;
//if (isset($options['LIMIT'])) {
// $tailOpts .= $this->limitResult('', $options['LIMIT'],
// isset($options['OFFSET']) ? $options['OFFSET']
// : false);
//}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
if ( isset ( $noKeyOptions [ 'FOR UPDATE' ] ) ) $postLimitTail .= ' FOR UPDATE' ;
if ( isset ( $noKeyOptions [ 'LOCK IN SHARE MODE' ] ) ) $postLimitTail .= ' LOCK IN SHARE MODE' ;
if ( isset ( $noKeyOptions [ 'DISTINCT' ] ) || isset ( $noKeyOptions [ 'DISTINCTROW' ] ) ) $startOpts .= 'DISTINCT' ;
2005-03-28 07:56:17 +00:00
2008-03-30 09:48:15 +00:00
# Various MySQL extensions
if ( isset ( $noKeyOptions [ 'STRAIGHT_JOIN' ] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */' ;
if ( isset ( $noKeyOptions [ 'HIGH_PRIORITY' ] ) ) $startOpts .= ' HIGH_PRIORITY' ;
if ( isset ( $noKeyOptions [ 'SQL_BIG_RESULT' ] ) ) $startOpts .= ' SQL_BIG_RESULT' ;
if ( isset ( $noKeyOptions [ 'SQL_BUFFER_RESULT' ] ) ) $startOpts .= ' SQL_BUFFER_RESULT' ;
if ( isset ( $noKeyOptions [ 'SQL_SMALL_RESULT' ] ) ) $startOpts .= ' SQL_SMALL_RESULT' ;
if ( isset ( $noKeyOptions [ 'SQL_CALC_FOUND_ROWS' ] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS' ;
if ( isset ( $noKeyOptions [ 'SQL_CACHE' ] ) ) $startOpts .= ' SQL_CACHE' ;
if ( isset ( $noKeyOptions [ 'SQL_NO_CACHE' ] ) ) $startOpts .= ' SQL_NO_CACHE' ;
if ( isset ( $options [ 'USE INDEX' ] ) && ! is_array ( $options [ 'USE INDEX' ] ) ) {
$useIndex = $this -> useIndexClause ( $options [ 'USE INDEX' ] );
} else {
$useIndex = '' ;
}
return array ( $startOpts , $useIndex , $preLimitTail , $postLimitTail );
}
/**
* SELECT wrapper
*
* @ param mixed $table Array or string , table name ( s ) ( prefix auto - added )
* @ param mixed $vars Array or string , field name ( s ) to be retrieved
* @ param mixed $conds Array or string , condition ( s ) for WHERE
* @ param string $fname Calling function name ( use __METHOD__ ) for logs / profiling
* @ param array $options Associative array of options ( e . g . array ( 'GROUP BY' => 'page_title' )),
* see Database :: makeSelectOptions code for list of supported stuff
* @ return mixed Database result resource ( feed to Database :: fetchObject or whatever ), or false on failure
*/
function select ( $table , $vars , $conds = '' , $fname = 'Database::select' , $options = array () )
{
if ( is_array ( $vars ) ) {
$vars = implode ( ',' , $vars );
2004-01-10 16:44:31 +00:00
}
2008-03-30 09:48:15 +00:00
if ( ! is_array ( $options ) ) {
$options = array ( $options );
2005-03-28 07:56:17 +00:00
}
2008-03-30 09:48:15 +00:00
if ( is_array ( $table ) ) {
if ( isset ( $options [ 'USE INDEX' ] ) && is_array ( $options [ 'USE INDEX' ] ) )
$from = ' FROM ' . $this -> tableNamesWithUseIndex ( $table , $options [ 'USE INDEX' ] );
else
$from = ' FROM ' . implode ( ',' , array_map ( array ( & $this , 'tableName' ), $table ) );
} elseif ( $table != '' ) {
if ( $table { 0 } == ' ' ) {
$from = ' FROM ' . $table ;
2005-04-24 07:21:15 +00:00
} else {
2008-03-30 09:48:15 +00:00
$from = ' FROM ' . $this -> tableName ( $table );
2005-04-24 07:21:15 +00:00
}
2008-03-30 09:48:15 +00:00
} else {
$from = '' ;
2005-04-24 07:21:15 +00:00
}
2008-03-30 09:48:15 +00:00
list ( $startOpts , $useIndex , $preLimitTail , $postLimitTail ) = $this -> makeSelectOptions ( $options );
if ( ! empty ( $conds ) ) {
if ( is_array ( $conds ) ) {
$conds = $this -> makeList ( $conds , LIST_AND );
}
$sql = " SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail " ;
} else {
$sql = " SELECT $startOpts $vars $from $useIndex $preLimitTail " ;
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
if ( isset ( $options [ 'LIMIT' ]))
$sql = $this -> limitResult ( $sql , $options [ 'LIMIT' ],
isset ( $options [ 'OFFSET' ]) ? $options [ 'OFFSET' ] : false );
$sql = " $sql $postLimitTail " ;
if ( isset ( $options [ 'EXPLAIN' ])) {
$sql = 'EXPLAIN ' . $sql ;
2004-01-10 16:44:31 +00:00
}
2008-03-30 09:48:15 +00:00
return $this -> query ( $sql , $fname );
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Single row SELECT wrapper
* Aborts or returns FALSE on error
*
* $vars : the selected variables
* $conds : a condition map , terms are ANDed together .
* Items with numeric keys are taken to be literal conditions
* Takes an array of selected variables , and a condition map , which is ANDed
* 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
*
* @ todo migrate documentation to phpdocumentor format
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function selectRow ( $table , $vars , $conds , $fname = 'Database::selectRow' , $options = array () ) {
$options [ 'LIMIT' ] = 1 ;
$res = $this -> select ( $table , $vars , $conds , $fname , $options );
if ( $res === false )
return false ;
if ( ! $this -> numRows ( $res ) ) {
$this -> freeResult ( $res );
return false ;
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
$obj = $this -> fetchObject ( $res );
$this -> freeResult ( $res );
return $obj ;
2004-07-24 07:24:04 +00:00
2008-03-30 09:48:15 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Estimate rows in dataset
* Returns estimated count , based on EXPLAIN output
* Takes same arguments as Database :: select ()
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
$this -> freeResult ( $res );
return $rows ;
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
2004-09-03 16:36:46 +00:00
2004-10-18 07:25:56 +00:00
/**
2008-03-30 09:48:15 +00:00
* Removes most variables from an SQL query and replaces them with X or N for numbers .
* It 's only slightly flawed. Don' t use for anything important .
2004-10-18 07:25:56 +00:00
*
2008-03-30 09:48:15 +00:00
* @ param string $sql A SQL Query
* @ static
2004-10-18 07:25:56 +00:00
*/
2008-03-30 09:48:15 +00:00
static function generalizeSQL ( $sql ) {
# This does the same as the regexp below would do, but in such a way
# as to avoid crashing php on some large strings.
# $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
$sql = str_replace ( " \\ \\ " , '' , $sql );
$sql = str_replace ( " \\ ' " , '' , $sql );
$sql = str_replace ( " \\ \" " , '' , $sql );
$sql = preg_replace ( " /'.*'/s " , " 'X' " , $sql );
$sql = preg_replace ( '/".*"/s' , " 'X' " , $sql );
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
# All newlines, tabs, etc replaced by single space
$sql = preg_replace ( '/\s+/' , ' ' , $sql );
# All numbers => N
$sql = preg_replace ( '/-?[0-9]+/s' , 'N' , $sql );
return $sql ;
2004-10-18 07:25:56 +00:00
}
2005-08-02 13:35:19 +00:00
2004-10-18 07:25:56 +00:00
/**
2008-03-30 09:48:15 +00:00
* Determines whether a field exists in a table
* Usually aborts on failure
* If errors are explicitly ignored , returns NULL on failure
2004-10-18 07:25:56 +00:00
*/
2008-03-30 09:48:15 +00:00
function fieldExists ( $table , $field , $fname = 'Database::fieldExists' ) {
$table = $this -> tableName ( $table );
$res = $this -> query ( 'DESCRIBE ' . $table , $fname );
if ( ! $res ) {
return NULL ;
2004-10-18 07:25:56 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
$found = false ;
while ( $row = $this -> fetchObject ( $res ) ) {
if ( $row -> Field == $field ) {
$found = true ;
break ;
}
}
return $found ;
2004-10-18 07:25:56 +00:00
}
2005-08-02 13:35:19 +00:00
2004-10-18 07:25:56 +00:00
/**
2008-03-30 09:48:15 +00:00
* Determines whether an index exists
* Usually aborts on failure
* If errors are explicitly ignored , returns NULL on failure
2004-10-18 07:25:56 +00:00
*/
2008-03-30 09:48:15 +00:00
function indexExists ( $table , $index , $fname = 'Database::indexExists' ) {
$info = $this -> indexInfo ( $table , $index , $fname );
if ( is_null ( $info ) ) {
return NULL ;
} else {
return $info !== false ;
2004-10-18 07:25:56 +00:00
}
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get information about an index into an object
* Returns false if the index does not exist
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function indexInfo ( $table , $index , $fname = 'Database::indexInfo' ) {
# SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
# SHOW INDEX should work for 3.x and up:
# http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
$table = $this -> tableName ( $table );
$sql = 'SHOW INDEX FROM ' . $table ;
$res = $this -> query ( $sql , $fname );
if ( ! $res ) {
return NULL ;
2007-07-05 19:42:18 +00:00
}
2008-03-30 09:48:15 +00:00
$result = array ();
while ( $row = $this -> fetchObject ( $res ) ) {
if ( $row -> Key_name == $index ) {
$result [] = $row ;
}
2004-03-20 14:07:56 +00:00
}
2008-03-30 09:48:15 +00:00
$this -> freeResult ( $res );
return empty ( $result ) ? false : $result ;
2004-03-20 14:07:56 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Query whether a given table exists
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function tableExists ( $table ) {
$table = $this -> tableName ( $table );
$old = $this -> ignoreErrors ( true );
$res = $this -> query ( " SELECT 1 FROM $table LIMIT 1 " );
$this -> ignoreErrors ( $old );
if ( $res ) {
$this -> freeResult ( $res );
return true ;
} else {
return false ;
2004-03-11 09:06:13 +00:00
}
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* mysql_fetch_field () wrapper
* Returns false if the field doesn ' t exist
2007-04-19 02:01:19 +00:00
*
2008-03-30 09:48:15 +00:00
* @ param $table
* @ param $field
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 );
}
2004-06-10 13:02:27 +00:00
}
2008-03-30 09:48:15 +00:00
return false ;
2005-08-02 13:35:19 +00:00
}
2004-06-10 13:02:27 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* mysql_field_type () wrapper
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function fieldType ( $res , $index ) {
2007-07-05 19:42:18 +00:00
if ( $res instanceof ResultWrapper ) {
$res = $res -> result ;
}
2008-03-30 09:48:15 +00:00
return mysql_field_type ( $res , $index );
2004-03-11 09:06:13 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Determines if a given index is unique
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function indexUnique ( $table , $index ) {
$indexInfo = $this -> indexInfo ( $table , $index );
if ( ! $indexInfo ) {
return NULL ;
2007-07-05 19:42:18 +00:00
}
2008-03-30 09:48:15 +00:00
return ! $indexInfo [ 0 ] -> Non_unique ;
2007-07-05 19:42:18 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* INSERT wrapper , inserts an array into a table
*
* $a may be a single associative array , or an array of these with numeric keys , for
* multi - row insert .
*
* Usually aborts on failure
* If errors are explicitly ignored , returns success
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function insert ( $table , $a , $fname = 'Database::insert' , $options = array () ) {
# No rows to insert, easy just return now
if ( ! count ( $a ) ) {
return true ;
2007-07-05 19:42:18 +00:00
}
2008-03-30 09:48:15 +00:00
$table = $this -> tableName ( $table );
if ( ! is_array ( $options ) ) {
$options = array ( $options );
}
if ( isset ( $a [ 0 ] ) && is_array ( $a [ 0 ] ) ) {
$multi = true ;
$keys = array_keys ( $a [ 0 ] );
} else {
$multi = false ;
$keys = array_keys ( $a );
}
$sql = 'INSERT ' . implode ( ' ' , $options ) .
" INTO $table ( " . implode ( ',' , $keys ) . ') VALUES ' ;
if ( $multi ) {
$first = true ;
foreach ( $a as $row ) {
if ( $first ) {
$first = false ;
} else {
$sql .= ',' ;
}
$sql .= '(' . $this -> makeList ( $row ) . ')' ;
}
} else {
$sql .= '(' . $this -> makeList ( $a ) . ')' ;
}
return ( bool ) $this -> query ( $sql , $fname );
2007-07-05 19:42:18 +00:00
}
2004-10-24 07:10:33 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Make UPDATE options for the Database :: update function
2004-10-24 07:10:33 +00:00
*
2008-03-30 09:48:15 +00:00
* @ private
* @ param array $options The options passed to Database :: update
* @ return string
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function makeUpdateOptions ( $options ) {
if ( ! is_array ( $options ) ) {
$options = array ( $options );
}
$opts = array ();
if ( in_array ( 'LOW_PRIORITY' , $options ) )
$opts [] = $this -> lowPriorityOption ();
if ( in_array ( 'IGNORE' , $options ) )
$opts [] = 'IGNORE' ;
return implode ( ' ' , $opts );
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* UPDATE wrapper , takes a condition array and a SET array
*
* @ param string $table The table to UPDATE
* @ param array $values An array of values to SET
* @ param array $conds An array of conditions ( WHERE ) . Use '*' to update all rows .
* @ param string $fname The Class :: Function calling this function
* ( for the log )
* @ param array $options An array of UPDATE options , can be one or
* more of IGNORE , LOW_PRIORITY
* @ return bool
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function update ( $table , $values , $conds , $fname = 'Database::update' , $options = array () ) {
$table = $this -> tableName ( $table );
$opts = $this -> makeUpdateOptions ( $options );
$sql = " UPDATE $opts $table SET " . $this -> makeList ( $values , LIST_SET );
if ( $conds != '*' ) {
$sql .= " WHERE " . $this -> makeList ( $conds , LIST_AND );
2007-07-05 19:42:18 +00:00
}
2008-03-30 09:48:15 +00:00
return $this -> query ( $sql , $fname );
2007-07-05 19:42:18 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Makes an encoded list of strings from an array
* $mode :
* LIST_COMMA - comma separated , no field names
* LIST_AND - ANDed WHERE clause ( without the WHERE )
* LIST_OR - ORed WHERE clause ( without the WHERE )
* LIST_SET - comma separated with field names , like a SET clause
* LIST_NAMES - comma separated field names
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function makeList ( $a , $mode = LIST_COMMA ) {
if ( ! is_array ( $a ) ) {
throw new DBUnexpectedError ( $this , 'Database::makeList called with incorrect parameters' );
}
$first = true ;
$list = '' ;
foreach ( $a as $field => $value ) {
if ( ! $first ) {
if ( $mode == LIST_AND ) {
$list .= ' AND ' ;
} elseif ( $mode == LIST_OR ) {
$list .= ' OR ' ;
} else {
$list .= ',' ;
}
} else {
$first = false ;
}
if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric ( $field ) ) {
$list .= " ( $value ) " ;
} elseif ( ( $mode == LIST_SET ) && is_numeric ( $field ) ) {
$list .= " $value " ;
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array ( $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 " ;
} elseif ( $mode == LIST_SET ) {
$list .= " $field = " ;
}
$list .= 'NULL' ;
} else {
if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
$list .= " $field = " ;
}
$list .= $mode == LIST_NAMES ? $value : $this -> addQuotes ( $value );
}
2005-01-14 13:00:17 +00:00
}
2008-03-30 09:48:15 +00:00
return $list ;
2005-01-14 13:00:17 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Change the current database
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function selectDB ( $db ) {
$this -> mDBname = $db ;
return mysql_select_db ( $db , $this -> mConn );
2005-08-02 13:35:19 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get the current DB name
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function getDBname () {
return $this -> mDBname ;
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get the server hostname or IP address
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function getServer () {
return $this -> mServer ;
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Format a table name ready for use in constructing an SQL query
2005-05-03 07:47:08 +00:00
*
2008-03-30 09:48:15 +00:00
* This does two important things : it quotes table names which as necessary ,
* and it adds a table prefix if there is one .
2005-05-03 07:47:08 +00:00
*
2008-03-30 09:48:15 +00:00
* All functions of this object which require a table name call this function
* themselves . Pass the canonical name to such functions . This is only needed
* when calling query () directly .
2006-12-29 20:56:47 +00:00
*
2008-03-30 09:48:15 +00:00
* @ param string $name database table name
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function tableName ( $name ) {
global $wgSharedDB ;
# Skip quoted literals
if ( $name { 0 } != '`' ) {
if ( $this -> mTablePrefix !== '' && strpos ( $name , '.' ) === false ) {
$name = " { $this -> mTablePrefix } $name " ;
2007-02-11 11:29:33 +00:00
}
2008-03-30 09:48:15 +00:00
if ( isset ( $wgSharedDB ) && " { $this -> mTablePrefix } user " == $name ) {
$name = " ` $wgSharedDB `.` $name ` " ;
} else {
# Standard quoting
$name = " ` $name ` " ;
2004-07-18 08:48:43 +00:00
}
2007-04-07 07:35:54 +00:00
}
2008-03-30 09:48:15 +00:00
return $name ;
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Fetch a number of table names into an array
* This is handy when you need to construct SQL for joins
2004-09-03 16:36:46 +00:00
*
2008-03-30 09:48:15 +00:00
* Example :
* extract ( $dbr -> tableNames ( 'user' , 'watchlist' ));
* $sql = " SELECT wl_namespace,wl_title FROM $watchlist , $user
* WHERE wl_user = user_id AND wl_user = $nameWithQuotes " ;
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
public function tableNames () {
$inArray = func_get_args ();
$retVal = array ();
foreach ( $inArray as $name ) {
$retVal [ $name ] = $this -> tableName ( $name );
2004-01-17 05:49:39 +00:00
}
2008-03-30 09:48:15 +00:00
return $retVal ;
2004-01-17 05:49:39 +00:00
}
2007-04-07 07:35:54 +00:00
/**
2008-03-30 09:48:15 +00:00
* Fetch a number of table names into an zero - indexed numerical array
* This is handy when you need to construct SQL for joins
*
* Example :
* list ( $user , $watchlist ) = $dbr -> tableNamesN ( 'user' , 'watchlist' );
* $sql = " SELECT wl_namespace,wl_title FROM $watchlist , $user
* WHERE wl_user = user_id AND wl_user = $nameWithQuotes " ;
2007-04-07 07:35:54 +00:00
*/
2008-03-30 09:48:15 +00:00
public function tableNamesN () {
$inArray = func_get_args ();
$retVal = array ();
foreach ( $inArray as $name ) {
$retVal [] = $this -> tableName ( $name );
2007-04-07 07:35:54 +00:00
}
2008-03-30 09:48:15 +00:00
return $retVal ;
2007-04-07 07:35:54 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* @ private
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function tableNamesWithUseIndex ( $tables , $use_index ) {
$ret = array ();
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
foreach ( $tables as $table )
if ( @ $use_index [ $table ] !== null )
$ret [] = $this -> tableName ( $table ) . ' ' . $this -> useIndexClause ( implode ( ',' , ( array ) $use_index [ $table ] ) );
else
$ret [] = $this -> tableName ( $table );
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
return implode ( ',' , $ret );
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Wrapper for addslashes ()
* @ param string $s String to be slashed .
* @ return string slashed string .
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function strencode ( $s ) {
return mysql_real_escape_string ( $s , $this -> mConn );
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* If it ' s a string , adds quotes and backslashes
* Otherwise returns as - is
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function addQuotes ( $s ) {
if ( is_null ( $s ) ) {
return 'NULL' ;
2004-07-10 03:09:26 +00:00
} else {
2008-03-30 09:48:15 +00:00
# This will also quote numeric values. This should be harmless,
# and protects against weird problems that occur when they really
# _are_ strings such as article titles and string->number->string
# conversion is not 1:1.
return " ' " . $this -> strencode ( $s ) . " ' " ;
2004-07-10 03:09:26 +00:00
}
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Escape string for safe LIKE usage
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function escapeLike ( $s ) {
$s = $this -> strencode ( $s );
$s = str_replace ( array ( '%' , '_' ), array ( '\%' , '\_' ), $s );
return $s ;
2004-01-10 16:44:31 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* 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 ()
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function nextSequenceValue ( $seqName ) {
return NULL ;
2004-01-17 05:49:39 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* USE INDEX clause
* PostgreSQL doesn ' t have them and returns " "
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function useIndexClause ( $index ) {
return " FORCE INDEX ( $index ) " ;
2004-01-17 05:49:39 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* REPLACE query wrapper
* PostgreSQL simulates this with a DELETE followed by INSERT
* $row is the row to insert , an associative array
* $uniqueIndexes is an array of indexes . Each element may be either a
* field name or an array of field names
*
* It may be more efficient to leave off unique indexes which are unlikely to collide .
* However if you do this , you run the risk of encountering errors which wouldn ' t have
* occurred in MySQL
*
* @ todo migrate comment to phodocumentor format
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function replace ( $table , $uniqueIndexes , $rows , $fname = 'Database::replace' ) {
$table = $this -> tableName ( $table );
# Single row case
if ( ! is_array ( reset ( $rows ) ) ) {
$rows = array ( $rows );
2007-07-05 19:42:18 +00:00
}
2004-07-18 08:48:43 +00:00
2008-03-30 09:48:15 +00:00
$sql = " REPLACE INTO $table ( " . implode ( ',' , array_keys ( $rows [ 0 ] ) ) . ') VALUES ' ;
$first = true ;
foreach ( $rows as $row ) {
if ( $first ) {
$first = false ;
} else {
$sql .= ',' ;
}
$sql .= '(' . $this -> makeList ( $row ) . ')' ;
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
return $this -> query ( $sql , $fname );
2004-07-18 08:48:43 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* DELETE where the condition is a join
* MySQL does this with a multi - table DELETE syntax , PostgreSQL does it with sub - selects
2004-09-03 16:36:46 +00:00
*
2008-03-30 09:48:15 +00:00
* For safety , an empty $conds will not delete everything . If you want to delete all rows where the
* join condition matches , set $conds = '*'
2004-09-03 16:36:46 +00:00
*
2008-03-30 09:48:15 +00:00
* DO NOT put the join condition in $conds
*
* @ param string $delTable The table to delete from .
* @ param string $joinTable The other table .
* @ param string $delVar The variable to join on , in the first table .
* @ param string $joinVar The variable to join on , in the second table .
* @ param array $conds Condition array of field names mapped to variables , ANDed together in the WHERE clause
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function deleteJoin ( $delTable , $joinTable , $delVar , $joinVar , $conds , $fname = 'Database::deleteJoin' ) {
if ( ! $conds ) {
throw new DBUnexpectedError ( $this , 'Database::deleteJoin() called with empty $conds' );
2004-07-24 07:24:04 +00:00
}
2008-03-30 09:48:15 +00:00
$delTable = $this -> tableName ( $delTable );
$joinTable = $this -> tableName ( $joinTable );
$sql = " DELETE $delTable FROM $delTable , $joinTable WHERE $delVar = $joinVar " ;
if ( $conds != '*' ) {
$sql .= ' AND ' . $this -> makeList ( $conds , LIST_AND );
2004-07-10 03:09:26 +00:00
}
2004-07-24 07:24:04 +00:00
2008-03-30 09:48:15 +00:00
return $this -> query ( $sql , $fname );
}
2004-07-10 03:09:26 +00:00
2008-03-30 09:48:15 +00:00
/**
* Returns the size of a text field , or - 1 for " unlimited "
*/
function textFieldSize ( $table , $field ) {
$table = $this -> tableName ( $table );
$sql = " SHOW COLUMNS FROM $table LIKE \" $field\ " ; " ;
$res = $this -> query ( $sql , 'Database::textFieldSize' );
$row = $this -> fetchObject ( $res );
$this -> freeResult ( $res );
$m = array ();
if ( preg_match ( '/\((.*)\)/' , $row -> Type , $m ) ) {
$size = $m [ 1 ];
2004-07-10 03:09:26 +00:00
} else {
2008-03-30 09:48:15 +00:00
$size = - 1 ;
2004-01-17 05:49:39 +00:00
}
2008-03-30 09:48:15 +00:00
return $size ;
}
/**
* @ return string Returns the text of the low priority option if it is supported , or a blank string otherwise
*/
function lowPriorityOption () {
return 'LOW_PRIORITY' ;
2004-01-17 05:49:39 +00:00
}
2004-07-18 08:48:43 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* DELETE query wrapper
2005-07-18 02:30:04 +00:00
*
2008-03-30 09:48:15 +00:00
* Use $conds == " * " to delete all rows
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function delete ( $table , $conds , $fname = 'Database::delete' ) {
if ( ! $conds ) {
throw new DBUnexpectedError ( $this , 'Database::delete() called with no conditions' );
2005-07-22 11:29:15 +00:00
}
2008-03-30 09:48:15 +00:00
$table = $this -> tableName ( $table );
$sql = " DELETE FROM $table " ;
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this -> makeList ( $conds , LIST_AND );
}
return $this -> query ( $sql , $fname );
2005-07-18 02:30:04 +00:00
}
2006-01-07 13:31:29 +00:00
2005-07-18 02:30:04 +00:00
/**
2008-03-30 09:48:15 +00:00
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array ( 'dest1' => 'source1' , ... )
* Source items may be literals rather than field names , but strings should be quoted with Database :: addQuotes ()
* $conds may be " * " to copy the whole table
* srcTable may be an array of tables .
2005-07-18 02:30:04 +00:00
*/
2008-03-30 09:48:15 +00:00
function insertSelect ( $destTable , $srcTable , $varMap , $conds , $fname = 'Database::insertSelect' ,
$insertOptions = array (), $selectOptions = array () )
{
$destTable = $this -> tableName ( $destTable );
if ( is_array ( $insertOptions ) ) {
$insertOptions = implode ( ' ' , $insertOptions );
}
if ( ! is_array ( $selectOptions ) ) {
$selectOptions = array ( $selectOptions );
}
list ( $startOpts , $useIndex , $tailOpts ) = $this -> makeSelectOptions ( $selectOptions );
if ( is_array ( $srcTable ) ) {
$srcTable = implode ( ',' , array_map ( array ( & $this , 'tableName' ), $srcTable ) );
} else {
$srcTable = $this -> tableName ( $srcTable );
}
$sql = " INSERT $insertOptions INTO $destTable ( " . implode ( ',' , array_keys ( $varMap ) ) . ')' .
" SELECT $startOpts " . implode ( ',' , $varMap ) .
" FROM $srcTable $useIndex " ;
2005-06-19 00:21:49 +00:00
if ( $conds != '*' ) {
2008-03-30 09:48:15 +00:00
$sql .= ' WHERE ' . $this -> makeList ( $conds , LIST_AND );
2005-06-19 00:21:49 +00:00
}
2008-03-30 09:48:15 +00:00
$sql .= " $tailOpts " ;
2007-08-01 21:42:59 +00:00
return $this -> query ( $sql , $fname );
2004-03-23 10:13:59 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* 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 )
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function limitResult ( $sql , $limit , $offset = false ) {
if ( ! is_numeric ( $limit ) ) {
throw new DBUnexpectedError ( $this , " Invalid non-numeric limit passed to limitResult() \n " );
2004-07-10 03:09:26 +00:00
}
2008-03-30 09:48:15 +00:00
return " $sql LIMIT "
. ( ( is_numeric ( $offset ) && $offset != 0 ) ? " { $offset } , " : " " )
. " { $limit } " ;
}
function limitResultForUpdate ( $sql , $num ) {
return $this -> limitResult ( $sql , $num , 0 );
}
2004-07-10 03:09:26 +00:00
2008-03-30 09:48:15 +00:00
/**
* 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 ) " ;
2004-01-17 05:49:39 +00:00
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Determines if the last failure was due to a deadlock
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function wasDeadlock () {
return $this -> lastErrno () == 1213 ;
2004-01-10 16:44:31 +00:00
}
2004-02-11 13:03:58 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Perform a deadlock - prone transaction .
2005-08-02 13:35:19 +00:00
*
2008-03-30 09:48:15 +00:00
* 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 .
2005-08-02 13:35:19 +00:00
*
2008-03-30 09:48:15 +00:00
* Usage :
* $dbw -> deadlockLoop ( callback , ... );
2004-10-24 07:10:33 +00:00
*
2008-03-30 09:48:15 +00:00
* 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 .
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 );
}
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
} 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 ;
2005-04-10 18:26:26 +00:00
} else {
2008-03-30 09:48:15 +00:00
wfDebug ( " Fake slave up to date ( $wait us) \n " );
wfProfileOut ( $fname );
return 0 ;
2005-04-10 18:26:26 +00:00
}
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
# 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 ;
}
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get the position of the master from SHOW SLAVE STATUS
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 ;
2004-07-18 08:48:43 +00:00
}
}
2008-03-30 09:48:15 +00:00
Changing lines like this: "extract( $dbw->tableNames( 'page', 'archive' ) );" to be like this: "list ($page, $archive) = $dbw->tableNamesN( 'page', 'archive' );".
Three reasons for this:
1) It's better for analysis tools [which want explicit variable declaration]
2) It's easier for a human to read, as it's completely explicit where the variables came from [which is something you don't get with extract() ]
3) It makes it easier to find everywhere where a variable is used with search/grep [which you can't currently do with $tbl_page variables from things like: "extract($db->tableNames( 'page', 'revision'), EXTR_PREFIX_ALL, 'tbl');"].
Otherwise, from a functionality/efficiency perspective the two forms should be identical.
By doing this have been able run static analysis over the usages of these variables, thus eliminating 5 unneeded table names from calls, plus removing 3 unused calls entirely, and it just feels subjectively slightly nicer to me.
2006-11-27 08:36:57 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get the position of the master from SHOW MASTER STATUS
Changing lines like this: "extract( $dbw->tableNames( 'page', 'archive' ) );" to be like this: "list ($page, $archive) = $dbw->tableNamesN( 'page', 'archive' );".
Three reasons for this:
1) It's better for analysis tools [which want explicit variable declaration]
2) It's easier for a human to read, as it's completely explicit where the variables came from [which is something you don't get with extract() ]
3) It makes it easier to find everywhere where a variable is used with search/grep [which you can't currently do with $tbl_page variables from things like: "extract($db->tableNames( 'page', 'revision'), EXTR_PREFIX_ALL, 'tbl');"].
Otherwise, from a functionality/efficiency perspective the two forms should be identical.
By doing this have been able run static analysis over the usages of these variables, thus eliminating 5 unneeded table names from calls, plus removing 3 unused calls entirely, and it just feels subjectively slightly nicer to me.
2006-11-27 08:36:57 +00:00
*/
2008-03-30 09:48:15 +00:00
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 ;
Changing lines like this: "extract( $dbw->tableNames( 'page', 'archive' ) );" to be like this: "list ($page, $archive) = $dbw->tableNamesN( 'page', 'archive' );".
Three reasons for this:
1) It's better for analysis tools [which want explicit variable declaration]
2) It's easier for a human to read, as it's completely explicit where the variables came from [which is something you don't get with extract() ]
3) It makes it easier to find everywhere where a variable is used with search/grep [which you can't currently do with $tbl_page variables from things like: "extract($db->tableNames( 'page', 'revision'), EXTR_PREFIX_ALL, 'tbl');"].
Otherwise, from a functionality/efficiency perspective the two forms should be identical.
By doing this have been able run static analysis over the usages of these variables, thus eliminating 5 unneeded table names from calls, plus removing 3 unused calls entirely, and it just feels subjectively slightly nicer to me.
2006-11-27 08:36:57 +00:00
}
}
2005-08-02 13:35:19 +00:00
2005-10-02 16:10:39 +00:00
/**
2008-03-30 09:48:15 +00:00
* Begin a transaction , committing any previously open transaction
2005-10-02 16:10:39 +00:00
*/
2008-03-30 09:48:15 +00:00
function begin ( $fname = 'Database::begin' ) {
$this -> query ( 'BEGIN' , $fname );
$this -> mTrxLevel = 1 ;
2005-10-02 16:10:39 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* End a transaction
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function commit ( $fname = 'Database::commit' ) {
$this -> query ( 'COMMIT' , $fname );
$this -> mTrxLevel = 0 ;
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Rollback a transaction .
* No - op on non - transactional databases .
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function rollback ( $fname = 'Database::rollback' ) {
$this -> query ( 'ROLLBACK' , $fname , true );
$this -> mTrxLevel = 0 ;
2004-07-10 03:09:26 +00:00
}
2005-08-02 13:35:19 +00:00
2005-08-09 13:25:42 +00:00
/**
2008-03-30 09:48:15 +00:00
* Begin a transaction , committing any previously open transaction
* @ deprecated use begin ()
2005-08-09 13:25:42 +00:00
*/
2008-03-30 09:48:15 +00:00
function immediateBegin ( $fname = 'Database::immediateBegin' ) {
$this -> begin ();
2005-08-09 13:25:42 +00:00
}
2006-01-07 13:31:29 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Commit transaction , if one is open
* @ deprecated use commit ()
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function immediateCommit ( $fname = 'Database::immediateCommit' ) {
$this -> commit ();
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Return MW - style timestamp used for MySQL schema
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function timestamp ( $ts = 0 ) {
return wfTimestamp ( TS_MW , $ts );
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Local database timestamp format or null
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function timestampOrNull ( $ts = null ) {
if ( is_null ( $ts ) ) {
return null ;
} else {
return $this -> timestamp ( $ts );
2004-07-10 03:09:26 +00:00
}
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* @ todo document
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 );
2004-07-10 03:09:26 +00:00
}
2008-03-30 09:48:15 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
/**
* Return aggregated value alias
*/
function aggregateValue ( $valuedata , $valuename = 'value' ) {
return $valuename ;
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* @ return string wikitext of a link to the server software ' s web site
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function getSoftwareLink () {
return " [http://www.mysql.com/ MySQL] " ;
}
2004-07-10 03:09:26 +00:00
2008-03-30 09:48:15 +00:00
/**
* @ return string Version information from the database
*/
function getServerVersion () {
return mysql_get_server_info ( $this -> mConn );
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Ping the server and try to reconnect if it there is no connection
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 ;
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get slave lag .
* At the moment , this will only work if the DB user has the PROCESS privilege
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function getLag () {
if ( ! is_null ( $this -> mFakeSlaveLag ) ) {
wfDebug ( " getLag: fake slave lagged { $this -> mFakeSlaveLag } seconds \n " );
return $this -> mFakeSlaveLag ;
2004-07-10 03:09:26 +00:00
}
2008-03-30 09:48:15 +00:00
$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 ;
}
}
2004-07-10 03:09:26 +00:00
}
2008-03-30 09:48:15 +00:00
return false ;
2004-07-10 03:09:26 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Get status information from SHOW STATUS in an associative array
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function getStatus ( $which = " % " ) {
$res = $this -> query ( " SHOW STATUS LIKE ' { $which } ' " );
$status = array ();
while ( $row = $this -> fetchObject ( $res ) ) {
$status [ $row -> Variable_name ] = $row -> Value ;
2004-07-10 03:09:26 +00:00
}
2008-03-30 09:48:15 +00:00
return $status ;
2004-07-10 03:09:26 +00:00
}
2004-07-15 14:50:22 +00:00
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Return the maximum number of items allowed in a list , or 0 for unlimited .
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function maxListLen () {
return 0 ;
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
function encodeBlob ( $b ) {
return $b ;
}
function decodeBlob ( $b ) {
return $b ;
2004-07-15 14:50:22 +00:00
}
2004-07-18 08:48:43 +00:00
2004-09-09 00:02:38 +00:00
/**
2008-03-30 09:48:15 +00:00
* 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
2004-09-09 00:02:38 +00:00
*/
2008-03-30 09:48:15 +00:00
public function setTimeout ( $timeout ) {
$this -> query ( " SET net_read_timeout= $timeout " );
$this -> query ( " SET net_write_timeout= $timeout " );
2004-09-09 00:02:38 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* 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
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
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 ;
2004-07-18 08:48:43 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* 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
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
function sourceStream ( $fp , $lineCallback = false , $resultCallback = false ) {
$cmd = " " ;
$done = false ;
$dollarquote = false ;
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
while ( ! feof ( $fp ) ) {
if ( $lineCallback ) {
call_user_func ( $lineCallback );
}
$line = trim ( fgets ( $fp , 1024 ) );
$sl = strlen ( $line ) - 1 ;
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
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 ;
2004-07-18 08:48:43 +00:00
}
}
2008-03-30 09:48:15 +00:00
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 ;
}
/**
* Replace variables in sourced SQL
*/
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 );
}
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
// Table prefixes
$ins = preg_replace_callback ( '/\/\*(?:\$wgDBprefix|_)\*\/([a-z_]*)/' ,
array ( & $this , 'tableNameCallback' ), $ins );
return $ins ;
2004-07-18 08:48:43 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2008-03-30 09:48:15 +00:00
* Table name callback
* @ private
2004-09-03 16:36:46 +00:00
*/
2008-03-30 09:48:15 +00:00
protected function tableNameCallback ( $matches ) {
return $this -> tableName ( $matches [ 1 ] );
}
/*
* Build a concatenation list to feed into a SQL query
*/
function buildConcat ( $stringList ) {
return 'CONCAT(' . implode ( ',' , $stringList ) . ')' ;
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
/**
* 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 ;
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function DBObject ( $data ) {
$this -> mData = $data ;
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
function isLOB () {
return false ;
2004-07-18 08:48:43 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function data () {
return $this -> mData ;
2004-07-18 08:48:43 +00:00
}
2008-03-30 09:48:15 +00:00
}
2004-07-24 07:24:04 +00:00
2008-03-30 09:48:15 +00:00
/**
* 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 ;
2004-07-24 07:24:04 +00:00
}
2008-03-30 09:48:15 +00:00
function fetch () {
return $this -> mData ;
2004-07-24 07:24:04 +00:00
}
2008-03-30 09:48:15 +00:00
}
2004-07-24 07:24:04 +00:00
2008-03-30 09:48:15 +00:00
/**
* 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 ;
2004-07-24 07:24:04 +00:00
}
2008-03-30 09:48:15 +00:00
function name () {
return $this -> name ;
2004-07-24 07:24:04 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function tableName () {
return $this -> tableName ;
2004-07-24 07:24:04 +00:00
}
2004-08-10 11:12:18 +00:00
2008-03-30 09:48:15 +00:00
function defaultValue () {
return $this -> default ;
2004-08-10 11:12:18 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function maxLength () {
return $this -> max_length ;
2005-04-25 18:38:43 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function nullable () {
return $this -> nullable ;
2004-09-02 02:43:13 +00:00
}
2004-09-09 12:04:39 +00:00
2008-03-30 09:48:15 +00:00
function isKey () {
return $this -> is_key ;
2004-09-09 12:04:39 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function isMultipleKey () {
return $this -> is_multiple ;
2004-09-08 20:36:41 +00:00
}
2005-08-02 13:35:19 +00:00
2008-03-30 09:48:15 +00:00
function type () {
return $this -> type ;
2004-09-08 20:36:41 +00:00
}
2008-03-30 09:48:15 +00:00
}
2005-04-24 07:21:15 +00:00
2008-03-30 09:48:15 +00:00
/******************************************************************************
* Error classes
*****************************************************************************/
2008-03-10 04:08:44 +00:00
2008-03-30 09:48:15 +00:00
/**
* Database error base class
* @ addtogroup Database
*/
class DBError extends MWException {
public $db ;
2005-08-02 13:35:19 +00:00
2005-06-01 06:18:49 +00:00
/**
2008-03-30 09:48:15 +00:00
* 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
2005-06-01 06:18:49 +00:00
*/
2008-03-30 09:48:15 +00:00
function __construct ( Database & $db , $error ) {
$this -> db =& $db ;
parent :: __construct ( $error );
2005-06-01 06:18:49 +00:00
}
2008-03-30 09:48:15 +00:00
}
2005-06-01 06:18:49 +00:00
2008-03-30 09:48:15 +00:00
/**
* @ addtogroup Database
*/
class DBConnectionError extends DBError {
public $error ;
function __construct ( Database & $db , $error = 'unknown error' ) {
$msg = 'DB connection error' ;
if ( trim ( $error ) != '' ) {
$msg .= " : $error " ;
2005-06-01 06:18:49 +00:00
}
2008-03-30 09:48:15 +00:00
$this -> error = $error ;
parent :: __construct ( $db , $msg );
2005-08-02 13:35:19 +00:00
}
2008-03-30 09:48:15 +00:00
function useOutputPage () {
// Not likely to work
return false ;
2005-08-02 13:35:19 +00:00
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
function useMessageCache () {
// Not likely to work
return false ;
}
function getText () {
return $this -> getMessage () . " \n " ;
2006-07-05 03:56:48 +00:00
}
2008-03-30 09:48:15 +00:00
function getLogMessage () {
# Don't send to the exception log
return false ;
2007-03-09 02:04:36 +00:00
}
2008-03-30 09:48:15 +00:00
function getPageTitle () {
global $wgSitename ;
return " $wgSitename has a problem " ;
2007-04-22 14:04:06 +00:00
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
function getHTML () {
global $wgTitle , $wgUseFileCache , $title , $wgInputEncoding ;
global $wgSitename , $wgServer , $wgMessageCache ;
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
# I give up, Brion is right. Getting the message cache to work when there is no DB is tricky.
# Hard coding strings instead.
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
$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 ;
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
$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. " ;
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
# No database access
if ( is_object ( $wgMessageCache ) ) {
$wgMessageCache -> disable ();
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
if ( trim ( $this -> error ) == '' ) {
$this -> error = $this -> db -> getProperty ( 'mServer' );
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
$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 );
2006-01-17 11:48:18 +00:00
}
2008-03-30 09:48:15 +00:00
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
$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 () );
2006-01-17 11:48:18 +00:00
}
}
2008-03-30 09:48:15 +00:00
return $text ;
2006-01-17 11:48:18 +00:00
}
2008-03-30 09:48:15 +00:00
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
/**
* @ 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 " ;
2007-04-22 14:04:06 +00:00
2008-03-30 09:48:15 +00:00
parent :: __construct ( $db , $message );
$this -> error = $error ;
$this -> errno = $errno ;
$this -> sql = $sql ;
$this -> fname = $fname ;
}
2006-01-17 11:48:18 +00:00
2008-03-30 09:48:15 +00:00
function getText () {
if ( $this -> useMessageCache () ) {
return wfMsg ( 'dberrortextcl' , htmlspecialchars ( $this -> getSQL () ),
htmlspecialchars ( $this -> fname ), $this -> errno , htmlspecialchars ( $this -> error ) ) . " \n " ;
} else {
return $this -> getMessage ();
2006-01-17 11:48:18 +00:00
}
}
2008-03-30 09:48:15 +00:00
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 ;
2006-01-17 11:48:18 +00:00
}
2008-03-30 09:48:15 +00:00
function getPageTitle () {
return $this -> msg ( 'databaseerror' , 'Database error' );
2007-09-23 22:23:01 +00:00
}
2008-03-30 09:48:15 +00:00
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 () ) );
}
}
2005-08-02 13:35:19 +00:00
}
2004-01-10 16:44:31 +00:00
2004-09-02 23:28:24 +00:00
/**
2007-04-20 08:55:14 +00:00
* @ addtogroup Database
2004-09-02 23:28:24 +00:00
*/
2008-03-30 09:48:15 +00:00
class DBUnexpectedError extends DBError {}
2004-07-10 03:09:26 +00:00
2004-09-02 02:43:13 +00:00
2004-09-02 23:28:24 +00:00
/**
* Result wrapper for grabbing data queried by someone else
2007-04-20 08:55:14 +00:00
* @ addtogroup Database
2004-09-02 23:28:24 +00:00
*/
2007-07-09 00:48:40 +00:00
class ResultWrapper implements Iterator {
var $db , $result , $pos = 0 , $currentRow = null ;
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2007-07-05 19:42:18 +00:00
* Create a new result object from a result resource and a Database object
2004-09-03 16:36:46 +00:00
*/
2007-07-05 19:42:18 +00:00
function ResultWrapper ( $database , $result ) {
$this -> db = $database ;
if ( $result instanceof ResultWrapper ) {
$this -> result = $result -> result ;
} else {
$this -> result = $result ;
}
2004-09-02 02:43:13 +00:00
}
2004-09-03 16:36:46 +00:00
/**
2007-07-05 19:42:18 +00:00
* Get the number of rows in a result object
2004-09-03 16:36:46 +00:00
*/
2004-09-02 02:43:13 +00:00
function numRows () {
return $this -> db -> numRows ( $this -> result );
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2007-07-05 19:42:18 +00:00
* Fetch the next row from the given result object , in object form .
* Fields can be retrieved with $row -> fieldname , with fields acting like
* member variables .
*
* @ param $res SQL result object as returned from Database :: query (), etc .
* @ return MySQL row object
* @ throws DBUnexpectedError Thrown if the database returns an error
2004-09-03 16:36:46 +00:00
*/
2005-07-22 11:29:15 +00:00
function fetchObject () {
2004-09-02 02:43:13 +00:00
return $this -> db -> fetchObject ( $this -> result );
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2007-07-05 19:42:18 +00:00
* Fetch the next row from the given result object , in associative array
* form . Fields are retrieved with $row [ 'fieldname' ] .
*
* @ param $res SQL result object as returned from Database :: query (), etc .
* @ return MySQL row object
* @ throws DBUnexpectedError Thrown if the database returns an error
2004-09-03 16:36:46 +00:00
*/
2006-06-18 12:30:57 +00:00
function fetchRow () {
2004-09-02 02:43:13 +00:00
return $this -> db -> fetchRow ( $this -> result );
}
2005-08-02 13:35:19 +00:00
2004-09-03 16:36:46 +00:00
/**
2007-07-05 19:42:18 +00:00
* Free a result object
2004-09-03 16:36:46 +00:00
*/
2004-09-02 02:43:13 +00:00
function free () {
$this -> db -> freeResult ( $this -> result );
unset ( $this -> result );
unset ( $this -> db );
}
2005-04-03 07:27:25 +00:00
2007-07-05 19:42:18 +00:00
/**
* Change the position of the cursor in a result object
* See mysql_data_seek ()
*/
2005-04-03 07:27:25 +00:00
function seek ( $row ) {
$this -> db -> dataSeek ( $this -> result , $row );
}
2007-07-09 00:48:40 +00:00
/*********************
* Iterator functions
* Note that using these in combination with the non - iterator functions
* above may cause rows to be skipped or repeated .
2007-07-05 19:42:18 +00:00
*/
2007-07-09 00:48:40 +00:00
2007-02-15 15:38:28 +00:00
function rewind () {
if ( $this -> numRows ()) {
$this -> db -> dataSeek ( $this -> result , 0 );
}
2007-07-09 00:48:40 +00:00
$this -> pos = 0 ;
$this -> currentRow = null ;
}
function current () {
if ( is_null ( $this -> currentRow ) ) {
$this -> next ();
}
return $this -> currentRow ;
}
function key () {
return $this -> pos ;
2007-02-15 15:38:28 +00:00
}
2006-01-17 11:48:18 +00:00
2007-07-09 00:48:40 +00:00
function next () {
$this -> pos ++ ;
$this -> currentRow = $this -> fetchObject ();
return $this -> currentRow ;
}
function valid () {
return $this -> current () !== false ;
}
2004-09-02 02:43:13 +00:00
}
2008-03-30 09:48:15 +00:00
class MySQLMasterPos {
var $file , $pos ;
2007-06-29 01:19:14 +00:00
2008-03-30 09:48:15 +00:00
function __construct ( $file , $pos ) {
$this -> file = $file ;
$this -> pos = $pos ;
}
function __toString () {
return " { $this -> file } / { $this -> pos } " ;
}
}