MediaWiki  REL1_21
Database.php
Go to the documentation of this file.
00001 <?php
00028 define( 'DEADLOCK_TRIES', 4 );
00030 define( 'DEADLOCK_DELAY_MIN', 500000 );
00032 define( 'DEADLOCK_DELAY_MAX', 1500000 );
00033 
00041 interface DatabaseType {
00047         function getType();
00048 
00059         function open( $server, $user, $password, $dbName );
00060 
00071         function fetchObject( $res );
00072 
00082         function fetchRow( $res );
00083 
00090         function numRows( $res );
00091 
00099         function numFields( $res );
00100 
00109         function fieldName( $res, $n );
00110 
00123         function insertId();
00124 
00132         function dataSeek( $res, $row );
00133 
00140         function lastErrno();
00141 
00148         function lastError();
00149 
00159         function fieldInfo( $table, $field );
00160 
00168         function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
00169 
00176         function affectedRows();
00177 
00184         function strencode( $s );
00185 
00194         static function getSoftwareLink();
00195 
00202         function getServerVersion();
00203 
00211         function getServerInfo();
00212 }
00213 
00218 abstract class DatabaseBase implements DatabaseType {
00219 
00220 # ------------------------------------------------------------------------------
00221 # Variables
00222 # ------------------------------------------------------------------------------
00223 
00224         protected $mLastQuery = '';
00225         protected $mDoneWrites = false;
00226         protected $mPHPError = false;
00227 
00228         protected $mServer, $mUser, $mPassword, $mDBname;
00229 
00230         protected $mConn = null;
00231         protected $mOpened = false;
00232 
00237         protected $mTrxIdleCallbacks = array();
00238 
00239         protected $mTablePrefix;
00240         protected $mFlags;
00241         protected $mTrxLevel = 0;
00242         protected $mErrorCount = 0;
00243         protected $mLBInfo = array();
00244         protected $mFakeSlaveLag = null, $mFakeMaster = false;
00245         protected $mDefaultBigSelects = null;
00246         protected $mSchemaVars = false;
00247 
00248         protected $preparedArgs;
00249 
00250         protected $htmlErrors;
00251 
00252         protected $delimiter = ';';
00253 
00261         private $mTrxFname = null;
00262 
00269         private $mTrxDoneWrites = false;
00270 
00277         private $mTrxAutomatic = false;
00278 
00283         protected $fileHandle = null;
00284 
00285 # ------------------------------------------------------------------------------
00286 # Accessors
00287 # ------------------------------------------------------------------------------
00288         # These optionally set a variable and return the previous state
00289 
00297         public function getServerInfo() {
00298                 return $this->getServerVersion();
00299         }
00300 
00304         public function getDelimiter() {
00305                 return $this->delimiter;
00306         }
00307 
00317         public function debug( $debug = null ) {
00318                 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00319         }
00320 
00343         public function bufferResults( $buffer = null ) {
00344                 if ( is_null( $buffer ) ) {
00345                         return !(bool)( $this->mFlags & DBO_NOBUFFER );
00346                 } else {
00347                         return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00348                 }
00349         }
00350 
00362         public function ignoreErrors( $ignoreErrors = null ) {
00363                 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00364         }
00365 
00375         public function trxLevel( $level = null ) {
00376                 return wfSetVar( $this->mTrxLevel, $level );
00377         }
00378 
00384         public function errorCount( $count = null ) {
00385                 return wfSetVar( $this->mErrorCount, $count );
00386         }
00387 
00393         public function tablePrefix( $prefix = null ) {
00394                 return wfSetVar( $this->mTablePrefix, $prefix );
00395         }
00396 
00402         public function setFileHandle( $fh ) {
00403                 $this->fileHandle = $fh;
00404         }
00405 
00415         public function getLBInfo( $name = null ) {
00416                 if ( is_null( $name ) ) {
00417                         return $this->mLBInfo;
00418                 } else {
00419                         if ( array_key_exists( $name, $this->mLBInfo ) ) {
00420                                 return $this->mLBInfo[$name];
00421                         } else {
00422                                 return null;
00423                         }
00424                 }
00425         }
00426 
00435         public function setLBInfo( $name, $value = null ) {
00436                 if ( is_null( $value ) ) {
00437                         $this->mLBInfo = $name;
00438                 } else {
00439                         $this->mLBInfo[$name] = $value;
00440                 }
00441         }
00442 
00448         public function setFakeSlaveLag( $lag ) {
00449                 $this->mFakeSlaveLag = $lag;
00450         }
00451 
00457         public function setFakeMaster( $enabled = true ) {
00458                 $this->mFakeMaster = $enabled;
00459         }
00460 
00466         public function cascadingDeletes() {
00467                 return false;
00468         }
00469 
00475         public function cleanupTriggers() {
00476                 return false;
00477         }
00478 
00485         public function strictIPs() {
00486                 return false;
00487         }
00488 
00494         public function realTimestamps() {
00495                 return false;
00496         }
00497 
00503         public function implicitGroupby() {
00504                 return true;
00505         }
00506 
00513         public function implicitOrderby() {
00514                 return true;
00515         }
00516 
00523         public function searchableIPs() {
00524                 return false;
00525         }
00526 
00532         public function functionalIndexes() {
00533                 return false;
00534         }
00535 
00540         public function lastQuery() {
00541                 return $this->mLastQuery;
00542         }
00543 
00550         public function doneWrites() {
00551                 return $this->mDoneWrites;
00552         }
00553 
00560         public function writesOrCallbacksPending() {
00561                 return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks );
00562         }
00563 
00568         public function isOpen() {
00569                 return $this->mOpened;
00570         }
00571 
00584         public function setFlag( $flag ) {
00585                 global $wgDebugDBTransactions;
00586                 $this->mFlags |= $flag;
00587                 if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
00588                         wfDebug( "Implicit transactions are now  disabled.\n" );
00589                 }
00590         }
00591 
00597         public function clearFlag( $flag ) {
00598                 global $wgDebugDBTransactions;
00599                 $this->mFlags &= ~$flag;
00600                 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00601                         wfDebug( "Implicit transactions are now disabled.\n" );
00602                 }
00603         }
00604 
00611         public function getFlag( $flag ) {
00612                 return !!( $this->mFlags & $flag );
00613         }
00614 
00622         public function getProperty( $name ) {
00623                 return $this->$name;
00624         }
00625 
00629         public function getWikiID() {
00630                 if ( $this->mTablePrefix ) {
00631                         return "{$this->mDBname}-{$this->mTablePrefix}";
00632                 } else {
00633                         return $this->mDBname;
00634                 }
00635         }
00636 
00642         public function getSchemaPath() {
00643                 global $IP;
00644                 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
00645                         return "$IP/maintenance/" . $this->getType() . "/tables.sql";
00646                 } else {
00647                         return "$IP/maintenance/tables.sql";
00648                 }
00649         }
00650 
00651 # ------------------------------------------------------------------------------
00652 # Other functions
00653 # ------------------------------------------------------------------------------
00654 
00664         function __construct( $server = false, $user = false, $password = false, $dbName = false,
00665                 $flags = 0, $tablePrefix = 'get from global'
00666         ) {
00667                 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
00668 
00669                 $this->mFlags = $flags;
00670 
00671                 if ( $this->mFlags & DBO_DEFAULT ) {
00672                         if ( $wgCommandLineMode ) {
00673                                 $this->mFlags &= ~DBO_TRX;
00674                                 if ( $wgDebugDBTransactions ) {
00675                                         wfDebug( "Implicit transaction open disabled.\n" );
00676                                 }
00677                         } else {
00678                                 $this->mFlags |= DBO_TRX;
00679                                 if ( $wgDebugDBTransactions ) {
00680                                         wfDebug( "Implicit transaction open enabled.\n" );
00681                                 }
00682                         }
00683                 }
00684 
00686                 if ( $tablePrefix == 'get from global' ) {
00687                         $this->mTablePrefix = $wgDBprefix;
00688                 } else {
00689                         $this->mTablePrefix = $tablePrefix;
00690                 }
00691 
00692                 if ( $user ) {
00693                         $this->open( $server, $user, $password, $dbName );
00694                 }
00695         }
00696 
00702         public function __sleep() {
00703                 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
00704         }
00705 
00728         final public static function factory( $dbType, $p = array() ) {
00729                 $canonicalDBTypes = array(
00730                         'mysql', 'postgres', 'sqlite', 'oracle', 'mssql'
00731                 );
00732                 $dbType = strtolower( $dbType );
00733                 $class = 'Database' . ucfirst( $dbType );
00734 
00735                 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
00736                         return new $class(
00737                                 isset( $p['host'] ) ? $p['host'] : false,
00738                                 isset( $p['user'] ) ? $p['user'] : false,
00739                                 isset( $p['password'] ) ? $p['password'] : false,
00740                                 isset( $p['dbname'] ) ? $p['dbname'] : false,
00741                                 isset( $p['flags'] ) ? $p['flags'] : 0,
00742                                 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
00743                         );
00744                 } else {
00745                         return null;
00746                 }
00747         }
00748 
00749         protected function installErrorHandler() {
00750                 $this->mPHPError = false;
00751                 $this->htmlErrors = ini_set( 'html_errors', '0' );
00752                 set_error_handler( array( $this, 'connectionErrorHandler' ) );
00753         }
00754 
00758         protected function restoreErrorHandler() {
00759                 restore_error_handler();
00760                 if ( $this->htmlErrors !== false ) {
00761                         ini_set( 'html_errors', $this->htmlErrors );
00762                 }
00763                 if ( $this->mPHPError ) {
00764                         $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00765                         $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
00766                         return $error;
00767                 } else {
00768                         return false;
00769                 }
00770         }
00771 
00776         protected function connectionErrorHandler( $errno, $errstr ) {
00777                 $this->mPHPError = $errstr;
00778         }
00779 
00787         public function close() {
00788                 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
00789                         throw new MWException( "Transaction idle callbacks still pending." );
00790                 }
00791                 $this->mOpened = false;
00792                 if ( $this->mConn ) {
00793                         if ( $this->trxLevel() ) {
00794                                 if ( !$this->mTrxAutomatic ) {
00795                                         wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
00796                                                 " performing implicit commit before closing connection!" );
00797                                 }
00798 
00799                                 $this->commit( __METHOD__, 'flush' );
00800                         }
00801 
00802                         $ret = $this->closeConnection();
00803                         $this->mConn = false;
00804                         return $ret;
00805                 } else {
00806                         return true;
00807                 }
00808         }
00809 
00815         abstract protected function closeConnection();
00816 
00821         function reportConnectionError( $error = 'Unknown error' ) {
00822                 $myError = $this->lastError();
00823                 if ( $myError ) {
00824                         $error = $myError;
00825                 }
00826 
00827                 # New method
00828                 throw new DBConnectionError( $this, $error );
00829         }
00830 
00837         abstract protected function doQuery( $sql );
00838 
00847         public function isWriteQuery( $sql ) {
00848                 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
00849         }
00850 
00873         public function query( $sql, $fname = '', $tempIgnore = false ) {
00874                 $isMaster = !is_null( $this->getLBInfo( 'master' ) );
00875                 if ( !Profiler::instance()->isStub() ) {
00876                         # generalizeSQL will probably cut down the query to reasonable
00877                         # logging size most of the time. The substr is really just a sanity check.
00878 
00879                         if ( $isMaster ) {
00880                                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00881                                 $totalProf = 'DatabaseBase::query-master';
00882                         } else {
00883                                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00884                                 $totalProf = 'DatabaseBase::query';
00885                         }
00886 
00887                         wfProfileIn( $totalProf );
00888                         wfProfileIn( $queryProf );
00889                 }
00890 
00891                 $this->mLastQuery = $sql;
00892                 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
00893                         # Set a flag indicating that writes have been done
00894                         wfDebug( __METHOD__ . ": Writes done: $sql\n" );
00895                         $this->mDoneWrites = true;
00896                 }
00897 
00898                 # Add a comment for easy SHOW PROCESSLIST interpretation
00899                 global $wgUser;
00900                 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
00901                         $userName = $wgUser->getName();
00902                         if ( mb_strlen( $userName ) > 15 ) {
00903                                 $userName = mb_substr( $userName, 0, 15 ) . '...';
00904                         }
00905                         $userName = str_replace( '/', '', $userName );
00906                 } else {
00907                         $userName = '';
00908                 }
00909 
00910                 // Add trace comment to the begin of the sql string, right after the operator.
00911                 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
00912                 $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
00913 
00914                 # If DBO_TRX is set, start a transaction
00915                 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
00916                         $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
00917                 {
00918                         # Avoid establishing transactions for SHOW and SET statements too -
00919                         # that would delay transaction initializations to once connection
00920                         # is really used by application
00921                         $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
00922                         if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
00923                                 global $wgDebugDBTransactions;
00924                                 if ( $wgDebugDBTransactions ) {
00925                                         wfDebug( "Implicit transaction start.\n" );
00926                                 }
00927                                 $this->begin( __METHOD__ . " ($fname)" );
00928                                 $this->mTrxAutomatic = true;
00929                         }
00930                 }
00931 
00932                 # Keep track of whether the transaction has write queries pending
00933                 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
00934                         $this->mTrxDoneWrites = true;
00935                 }
00936 
00937                 if ( $this->debug() ) {
00938                         static $cnt = 0;
00939 
00940                         $cnt++;
00941                         $sqlx = substr( $commentedSql, 0, 500 );
00942                         $sqlx = strtr( $sqlx, "\t\n", '  ' );
00943 
00944                         $master = $isMaster ? 'master' : 'slave';
00945                         wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
00946                 }
00947 
00948                 if ( istainted( $sql ) & TC_MYSQL ) {
00949                         throw new MWException( 'Tainted query found' );
00950                 }
00951 
00952                 $queryId = MWDebug::query( $sql, $fname, $isMaster );
00953 
00954                 # Do the query and handle errors
00955                 $ret = $this->doQuery( $commentedSql );
00956 
00957                 MWDebug::queryTime( $queryId );
00958 
00959                 # Try reconnecting if the connection was lost
00960                 if ( false === $ret && $this->wasErrorReissuable() ) {
00961                         # Transaction is gone, like it or not
00962                         $this->mTrxLevel = 0;
00963                         $this->mTrxIdleCallbacks = array(); // cancel
00964                         wfDebug( "Connection lost, reconnecting...\n" );
00965 
00966                         if ( $this->ping() ) {
00967                                 wfDebug( "Reconnected\n" );
00968                                 $sqlx = substr( $commentedSql, 0, 500 );
00969                                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
00970                                 global $wgRequestTime;
00971                                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
00972                                 if ( $elapsed < 300 ) {
00973                                         # Not a database error to lose a transaction after a minute or two
00974                                         wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
00975                                 }
00976                                 $ret = $this->doQuery( $commentedSql );
00977                         } else {
00978                                 wfDebug( "Failed\n" );
00979                         }
00980                 }
00981 
00982                 if ( false === $ret ) {
00983                         $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
00984                 }
00985 
00986                 if ( !Profiler::instance()->isStub() ) {
00987                         wfProfileOut( $queryProf );
00988                         wfProfileOut( $totalProf );
00989                 }
00990 
00991                 return $this->resultObject( $ret );
00992         }
00993 
01005         public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
01006                 # Ignore errors during error handling to avoid infinite recursion
01007                 $ignore = $this->ignoreErrors( true );
01008                 ++$this->mErrorCount;
01009 
01010                 if ( $ignore || $tempIgnore ) {
01011                         wfDebug( "SQL ERROR (ignored): $error\n" );
01012                         $this->ignoreErrors( $ignore );
01013                 } else {
01014                         $sql1line = str_replace( "\n", "\\n", $sql );
01015                         wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
01016                         wfDebug( "SQL ERROR: " . $error . "\n" );
01017                         throw new DBQueryError( $this, $error, $errno, $sql, $fname );
01018                 }
01019         }
01020 
01035         protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
01036                 /* MySQL doesn't support prepared statements (yet), so just
01037                    pack up the query for reference. We'll manually replace
01038                    the bits later. */
01039                 return array( 'query' => $sql, 'func' => $func );
01040         }
01041 
01046         protected function freePrepared( $prepared ) {
01047                 /* No-op by default */
01048         }
01049 
01057         public function execute( $prepared, $args = null ) {
01058                 if ( !is_array( $args ) ) {
01059                         # Pull the var args
01060                         $args = func_get_args();
01061                         array_shift( $args );
01062                 }
01063 
01064                 $sql = $this->fillPrepared( $prepared['query'], $args );
01065 
01066                 return $this->query( $sql, $prepared['func'] );
01067         }
01068 
01076         public function fillPrepared( $preparedQuery, $args ) {
01077                 reset( $args );
01078                 $this->preparedArgs =& $args;
01079 
01080                 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01081                         array( &$this, 'fillPreparedArg' ), $preparedQuery );
01082         }
01083 
01093         protected function fillPreparedArg( $matches ) {
01094                 switch( $matches[1] ) {
01095                         case '\\?': return '?';
01096                         case '\\!': return '!';
01097                         case '\\&': return '&';
01098                 }
01099 
01100                 list( /* $n */, $arg ) = each( $this->preparedArgs );
01101 
01102                 switch( $matches[1] ) {
01103                         case '?': return $this->addQuotes( $arg );
01104                         case '!': return $arg;
01105                         case '&':
01106                                 # return $this->addQuotes( file_get_contents( $arg ) );
01107                                 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
01108                         default:
01109                                 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
01110                 }
01111         }
01112 
01120         public function freeResult( $res ) {}
01121 
01139         public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
01140                 $options = array() )
01141         {
01142                 if ( !is_array( $options ) ) {
01143                         $options = array( $options );
01144                 }
01145 
01146                 $options['LIMIT'] = 1;
01147 
01148                 $res = $this->select( $table, $var, $cond, $fname, $options );
01149 
01150                 if ( $res === false || !$this->numRows( $res ) ) {
01151                         return false;
01152                 }
01153 
01154                 $row = $this->fetchRow( $res );
01155 
01156                 if ( $row !== false ) {
01157                         return reset( $row );
01158                 } else {
01159                         return false;
01160                 }
01161         }
01162 
01172         public function makeSelectOptions( $options ) {
01173                 $preLimitTail = $postLimitTail = '';
01174                 $startOpts = '';
01175 
01176                 $noKeyOptions = array();
01177 
01178                 foreach ( $options as $key => $option ) {
01179                         if ( is_numeric( $key ) ) {
01180                                 $noKeyOptions[$option] = true;
01181                         }
01182                 }
01183 
01184                 $preLimitTail .= $this->makeGroupByWithHaving( $options );
01185 
01186                 $preLimitTail .= $this->makeOrderBy( $options );
01187 
01188                 // if (isset($options['LIMIT'])) {
01189                 //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
01190                 //              isset($options['OFFSET']) ? $options['OFFSET']
01191                 //              : false);
01192                 // }
01193 
01194                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01195                         $postLimitTail .= ' FOR UPDATE';
01196                 }
01197 
01198                 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01199                         $postLimitTail .= ' LOCK IN SHARE MODE';
01200                 }
01201 
01202                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01203                         $startOpts .= 'DISTINCT';
01204                 }
01205 
01206                 # Various MySQL extensions
01207                 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01208                         $startOpts .= ' /*! STRAIGHT_JOIN */';
01209                 }
01210 
01211                 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01212                         $startOpts .= ' HIGH_PRIORITY';
01213                 }
01214 
01215                 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01216                         $startOpts .= ' SQL_BIG_RESULT';
01217                 }
01218 
01219                 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01220                         $startOpts .= ' SQL_BUFFER_RESULT';
01221                 }
01222 
01223                 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01224                         $startOpts .= ' SQL_SMALL_RESULT';
01225                 }
01226 
01227                 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01228                         $startOpts .= ' SQL_CALC_FOUND_ROWS';
01229                 }
01230 
01231                 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01232                         $startOpts .= ' SQL_CACHE';
01233                 }
01234 
01235                 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01236                         $startOpts .= ' SQL_NO_CACHE';
01237                 }
01238 
01239                 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
01240                         $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01241                 } else {
01242                         $useIndex = '';
01243                 }
01244 
01245                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01246         }
01247 
01256         public function makeGroupByWithHaving( $options ) {
01257                 $sql = '';
01258                 if ( isset( $options['GROUP BY'] ) ) {
01259                         $gb = is_array( $options['GROUP BY'] )
01260                                 ? implode( ',', $options['GROUP BY'] )
01261                                 : $options['GROUP BY'];
01262                         $sql .= ' GROUP BY ' . $gb;
01263                 }
01264                 if ( isset( $options['HAVING'] ) ) {
01265                         $having = is_array( $options['HAVING'] )
01266                                 ? $this->makeList( $options['HAVING'], LIST_AND )
01267                                 : $options['HAVING'];
01268                         $sql .= ' HAVING ' . $having;
01269                 }
01270                 return $sql;
01271         }
01272 
01281         public function makeOrderBy( $options ) {
01282                 if ( isset( $options['ORDER BY'] ) ) {
01283                         $ob = is_array( $options['ORDER BY'] )
01284                                 ? implode( ',', $options['ORDER BY'] )
01285                                 : $options['ORDER BY'];
01286                         return ' ORDER BY ' . $ob;
01287                 }
01288                 return '';
01289         }
01290 
01430         public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01431                 $options = array(), $join_conds = array() ) {
01432                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01433 
01434                 return $this->query( $sql, $fname );
01435         }
01436 
01453         public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01454                 $options = array(), $join_conds = array() )
01455         {
01456                 if ( is_array( $vars ) ) {
01457                         $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
01458                 }
01459 
01460                 $options = (array)$options;
01461 
01462                 if ( is_array( $table ) ) {
01463                         $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01464                                 ? $options['USE INDEX']
01465                                 : array();
01466                         if ( count( $join_conds ) || count( $useIndex ) ) {
01467                                 $from = ' FROM ' .
01468                                         $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
01469                         } else {
01470                                 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
01471                         }
01472                 } elseif ( $table != '' ) {
01473                         if ( $table[0] == ' ' ) {
01474                                 $from = ' FROM ' . $table;
01475                         } else {
01476                                 $from = ' FROM ' . $this->tableName( $table );
01477                         }
01478                 } else {
01479                         $from = '';
01480                 }
01481 
01482                 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
01483 
01484                 if ( !empty( $conds ) ) {
01485                         if ( is_array( $conds ) ) {
01486                                 $conds = $this->makeList( $conds, LIST_AND );
01487                         }
01488                         $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01489                 } else {
01490                         $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01491                 }
01492 
01493                 if ( isset( $options['LIMIT'] ) ) {
01494                         $sql = $this->limitResult( $sql, $options['LIMIT'],
01495                                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01496                 }
01497                 $sql = "$sql $postLimitTail";
01498 
01499                 if ( isset( $options['EXPLAIN'] ) ) {
01500                         $sql = 'EXPLAIN ' . $sql;
01501                 }
01502 
01503                 return $sql;
01504         }
01505 
01520         public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
01521                 $options = array(), $join_conds = array() )
01522         {
01523                 $options = (array)$options;
01524                 $options['LIMIT'] = 1;
01525                 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01526 
01527                 if ( $res === false ) {
01528                         return false;
01529                 }
01530 
01531                 if ( !$this->numRows( $res ) ) {
01532                         return false;
01533                 }
01534 
01535                 $obj = $this->fetchObject( $res );
01536 
01537                 return $obj;
01538         }
01539 
01560         public function estimateRowCount( $table, $vars = '*', $conds = '',
01561                 $fname = 'DatabaseBase::estimateRowCount', $options = array() )
01562         {
01563                 $rows = 0;
01564                 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
01565 
01566                 if ( $res ) {
01567                         $row = $this->fetchRow( $res );
01568                         $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01569                 }
01570 
01571                 return $rows;
01572         }
01573 
01582         static function generalizeSQL( $sql ) {
01583                 # This does the same as the regexp below would do, but in such a way
01584                 # as to avoid crashing php on some large strings.
01585                 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
01586 
01587                 $sql = str_replace ( "\\\\", '', $sql );
01588                 $sql = str_replace ( "\\'", '', $sql );
01589                 $sql = str_replace ( "\\\"", '', $sql );
01590                 $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
01591                 $sql = preg_replace ( '/".*"/s', "'X'", $sql );
01592 
01593                 # All newlines, tabs, etc replaced by single space
01594                 $sql = preg_replace ( '/\s+/', ' ', $sql );
01595 
01596                 # All numbers => N
01597                 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
01598 
01599                 return $sql;
01600         }
01601 
01610         public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
01611                 $info = $this->fieldInfo( $table, $field );
01612 
01613                 return (bool)$info;
01614         }
01615 
01627         public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
01628                 if( !$this->tableExists( $table ) ) {
01629                         return null;
01630                 }
01631 
01632                 $info = $this->indexInfo( $table, $index, $fname );
01633                 if ( is_null( $info ) ) {
01634                         return null;
01635                 } else {
01636                         return $info !== false;
01637                 }
01638         }
01639 
01648         public function tableExists( $table, $fname = __METHOD__ ) {
01649                 $table = $this->tableName( $table );
01650                 $old = $this->ignoreErrors( true );
01651                 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01652                 $this->ignoreErrors( $old );
01653 
01654                 return (bool)$res;
01655         }
01656 
01663         public function fieldType( $res, $index ) {
01664                 if ( $res instanceof ResultWrapper ) {
01665                         $res = $res->result;
01666                 }
01667 
01668                 return mysql_field_type( $res, $index );
01669         }
01670 
01679         public function indexUnique( $table, $index ) {
01680                 $indexInfo = $this->indexInfo( $table, $index );
01681 
01682                 if ( !$indexInfo ) {
01683                         return null;
01684                 }
01685 
01686                 return !$indexInfo[0]->Non_unique;
01687         }
01688 
01695         protected function makeInsertOptions( $options ) {
01696                 return implode( ' ', $options );
01697         }
01698 
01732         public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
01733                 # No rows to insert, easy just return now
01734                 if ( !count( $a ) ) {
01735                         return true;
01736                 }
01737 
01738                 $table = $this->tableName( $table );
01739 
01740                 if ( !is_array( $options ) ) {
01741                         $options = array( $options );
01742                 }
01743 
01744                 $fh = null;
01745                 if ( isset( $options['fileHandle'] ) ) {
01746                         $fh = $options['fileHandle'];
01747                 }
01748                 $options = $this->makeInsertOptions( $options );
01749 
01750                 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01751                         $multi = true;
01752                         $keys = array_keys( $a[0] );
01753                 } else {
01754                         $multi = false;
01755                         $keys = array_keys( $a );
01756                 }
01757 
01758                 $sql = 'INSERT ' . $options .
01759                         " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01760 
01761                 if ( $multi ) {
01762                         $first = true;
01763                         foreach ( $a as $row ) {
01764                                 if ( $first ) {
01765                                         $first = false;
01766                                 } else {
01767                                         $sql .= ',';
01768                                 }
01769                                 $sql .= '(' . $this->makeList( $row ) . ')';
01770                         }
01771                 } else {
01772                         $sql .= '(' . $this->makeList( $a ) . ')';
01773                 }
01774 
01775                 if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
01776                         return false;
01777                 } elseif ( $fh !== null ) {
01778                         return true;
01779                 }
01780 
01781                 return (bool)$this->query( $sql, $fname );
01782         }
01783 
01790         protected function makeUpdateOptions( $options ) {
01791                 if ( !is_array( $options ) ) {
01792                         $options = array( $options );
01793                 }
01794 
01795                 $opts = array();
01796 
01797                 if ( in_array( 'LOW_PRIORITY', $options ) ) {
01798                         $opts[] = $this->lowPriorityOption();
01799                 }
01800 
01801                 if ( in_array( 'IGNORE', $options ) ) {
01802                         $opts[] = 'IGNORE';
01803                 }
01804 
01805                 return implode( ' ', $opts );
01806         }
01807 
01831         function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
01832                 $table = $this->tableName( $table );
01833                 $opts = $this->makeUpdateOptions( $options );
01834                 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01835 
01836                 if ( $conds !== array() && $conds !== '*' ) {
01837                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01838                 }
01839 
01840                 return $this->query( $sql, $fname );
01841         }
01842 
01857         public function makeList( $a, $mode = LIST_COMMA ) {
01858                 if ( !is_array( $a ) ) {
01859                         throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
01860                 }
01861 
01862                 $first = true;
01863                 $list = '';
01864 
01865                 foreach ( $a as $field => $value ) {
01866                         if ( !$first ) {
01867                                 if ( $mode == LIST_AND ) {
01868                                         $list .= ' AND ';
01869                                 } elseif ( $mode == LIST_OR ) {
01870                                         $list .= ' OR ';
01871                                 } else {
01872                                         $list .= ',';
01873                                 }
01874                         } else {
01875                                 $first = false;
01876                         }
01877 
01878                         if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
01879                                 $list .= "($value)";
01880                         } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
01881                                 $list .= "$value";
01882                         } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
01883                                 if ( count( $value ) == 0 ) {
01884                                         throw new MWException( __METHOD__ . ": empty input for field $field" );
01885                                 } elseif ( count( $value ) == 1 ) {
01886                                         // Special-case single values, as IN isn't terribly efficient
01887                                         // Don't necessarily assume the single key is 0; we don't
01888                                         // enforce linear numeric ordering on other arrays here.
01889                                         $value = array_values( $value );
01890                                         $list .= $field . " = " . $this->addQuotes( $value[0] );
01891                                 } else {
01892                                         $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
01893                                 }
01894                         } elseif ( $value === null ) {
01895                                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
01896                                         $list .= "$field IS ";
01897                                 } elseif ( $mode == LIST_SET ) {
01898                                         $list .= "$field = ";
01899                                 }
01900                                 $list .= 'NULL';
01901                         } else {
01902                                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
01903                                         $list .= "$field = ";
01904                                 }
01905                                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
01906                         }
01907                 }
01908 
01909                 return $list;
01910         }
01911 
01922         public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
01923                 $conds = array();
01924 
01925                 foreach ( $data as $base => $sub ) {
01926                         if ( count( $sub ) ) {
01927                                 $conds[] = $this->makeList(
01928                                         array( $baseKey => $base, $subKey => array_keys( $sub ) ),
01929                                         LIST_AND );
01930                         }
01931                 }
01932 
01933                 if ( $conds ) {
01934                         return $this->makeList( $conds, LIST_OR );
01935                 } else {
01936                         // Nothing to search for...
01937                         return false;
01938                 }
01939         }
01940 
01949         public function aggregateValue( $valuedata, $valuename = 'value' ) {
01950                 return $valuename;
01951         }
01952 
01957         public function bitNot( $field ) {
01958                 return "(~$field)";
01959         }
01960 
01966         public function bitAnd( $fieldLeft, $fieldRight ) {
01967                 return "($fieldLeft & $fieldRight)";
01968         }
01969 
01975         public function bitOr( $fieldLeft, $fieldRight ) {
01976                 return "($fieldLeft | $fieldRight)";
01977         }
01978 
01984         public function buildConcat( $stringList ) {
01985                 return 'CONCAT(' . implode( ',', $stringList ) . ')';
01986         }
01987 
01997         public function selectDB( $db ) {
01998                 # Stub.  Shouldn't cause serious problems if it's not overridden, but
01999                 # if your database engine supports a concept similar to MySQL's
02000                 # databases you may as well.
02001                 $this->mDBname = $db;
02002                 return true;
02003         }
02004 
02008         public function getDBname() {
02009                 return $this->mDBname;
02010         }
02011 
02015         public function getServer() {
02016                 return $this->mServer;
02017         }
02018 
02036         public function tableName( $name, $format = 'quoted' ) {
02037                 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
02038                 # Skip the entire process when we have a string quoted on both ends.
02039                 # Note that we check the end so that we will still quote any use of
02040                 # use of `database`.table. But won't break things if someone wants
02041                 # to query a database table with a dot in the name.
02042                 if ( $this->isQuotedIdentifier( $name ) ) {
02043                         return $name;
02044                 }
02045 
02046                 # Lets test for any bits of text that should never show up in a table
02047                 # name. Basically anything like JOIN or ON which are actually part of
02048                 # SQL queries, but may end up inside of the table value to combine
02049                 # sql. Such as how the API is doing.
02050                 # Note that we use a whitespace test rather than a \b test to avoid
02051                 # any remote case where a word like on may be inside of a table name
02052                 # surrounded by symbols which may be considered word breaks.
02053                 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
02054                         return $name;
02055                 }
02056 
02057                 # Split database and table into proper variables.
02058                 # We reverse the explode so that database.table and table both output
02059                 # the correct table.
02060                 $dbDetails = explode( '.', $name, 2 );
02061                 if ( count( $dbDetails ) == 2 ) {
02062                         list( $database, $table ) = $dbDetails;
02063                         # We don't want any prefix added in this case
02064                         $prefix = '';
02065                 } else {
02066                         list( $table ) = $dbDetails;
02067                         if ( $wgSharedDB !== null # We have a shared database
02068                                 && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
02069                                 && in_array( $table, $wgSharedTables ) # A shared table is selected
02070                         ) {
02071                                 $database = $wgSharedDB;
02072                                 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
02073                         } else {
02074                                 $database = null;
02075                                 $prefix = $this->mTablePrefix; # Default prefix
02076                         }
02077                 }
02078 
02079                 # Quote $table and apply the prefix if not quoted.
02080                 $tableName = "{$prefix}{$table}";
02081                 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) {
02082                         $tableName = $this->addIdentifierQuotes( $tableName );
02083                 }
02084 
02085                 # Quote $database and merge it with the table name if needed
02086                 if ( $database !== null ) {
02087                         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
02088                                 $database = $this->addIdentifierQuotes( $database );
02089                         }
02090                         $tableName = $database . '.' . $tableName;
02091                 }
02092 
02093                 return $tableName;
02094         }
02095 
02107         public function tableNames() {
02108                 $inArray = func_get_args();
02109                 $retVal = array();
02110 
02111                 foreach ( $inArray as $name ) {
02112                         $retVal[$name] = $this->tableName( $name );
02113                 }
02114 
02115                 return $retVal;
02116         }
02117 
02129         public function tableNamesN() {
02130                 $inArray = func_get_args();
02131                 $retVal = array();
02132 
02133                 foreach ( $inArray as $name ) {
02134                         $retVal[] = $this->tableName( $name );
02135                 }
02136 
02137                 return $retVal;
02138         }
02139 
02148         public function tableNameWithAlias( $name, $alias = false ) {
02149                 if ( !$alias || $alias == $name ) {
02150                         return $this->tableName( $name );
02151                 } else {
02152                         return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02153                 }
02154         }
02155 
02162         public function tableNamesWithAlias( $tables ) {
02163                 $retval = array();
02164                 foreach ( $tables as $alias => $table ) {
02165                         if ( is_numeric( $alias ) ) {
02166                                 $alias = $table;
02167                         }
02168                         $retval[] = $this->tableNameWithAlias( $table, $alias );
02169                 }
02170                 return $retval;
02171         }
02172 
02181         public function fieldNameWithAlias( $name, $alias = false ) {
02182                 if ( !$alias || (string)$alias === (string)$name ) {
02183                         return $name;
02184                 } else {
02185                         return $name . ' AS ' . $alias; //PostgreSQL needs AS
02186                 }
02187         }
02188 
02195         public function fieldNamesWithAlias( $fields ) {
02196                 $retval = array();
02197                 foreach ( $fields as $alias => $field ) {
02198                         if ( is_numeric( $alias ) ) {
02199                                 $alias = $field;
02200                         }
02201                         $retval[] = $this->fieldNameWithAlias( $field, $alias );
02202                 }
02203                 return $retval;
02204         }
02205 
02215         protected function tableNamesWithUseIndexOrJOIN(
02216                 $tables, $use_index = array(), $join_conds = array()
02217         ) {
02218                 $ret = array();
02219                 $retJOIN = array();
02220                 $use_index = (array)$use_index;
02221                 $join_conds = (array)$join_conds;
02222 
02223                 foreach ( $tables as $alias => $table ) {
02224                         if ( !is_string( $alias ) ) {
02225                                 // No alias? Set it equal to the table name
02226                                 $alias = $table;
02227                         }
02228                         // Is there a JOIN clause for this table?
02229                         if ( isset( $join_conds[$alias] ) ) {
02230                                 list( $joinType, $conds ) = $join_conds[$alias];
02231                                 $tableClause = $joinType;
02232                                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02233                                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02234                                         $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02235                                         if ( $use != '' ) {
02236                                                 $tableClause .= ' ' . $use;
02237                                         }
02238                                 }
02239                                 $on = $this->makeList( (array)$conds, LIST_AND );
02240                                 if ( $on != '' ) {
02241                                         $tableClause .= ' ON (' . $on . ')';
02242                                 }
02243 
02244                                 $retJOIN[] = $tableClause;
02245                         // Is there an INDEX clause for this table?
02246                         } elseif ( isset( $use_index[$alias] ) ) {
02247                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02248                                 $tableClause .= ' ' . $this->useIndexClause(
02249                                         implode( ',', (array)$use_index[$alias] ) );
02250 
02251                                 $ret[] = $tableClause;
02252                         } else {
02253                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02254 
02255                                 $ret[] = $tableClause;
02256                         }
02257                 }
02258 
02259                 // We can't separate explicit JOIN clauses with ',', use ' ' for those
02260                 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02261                 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02262 
02263                 // Compile our final table clause
02264                 return implode( ' ', array( $straightJoins, $otherJoins ) );
02265         }
02266 
02274         protected function indexName( $index ) {
02275                 // Backwards-compatibility hack
02276                 $renamed = array(
02277                         'ar_usertext_timestamp' => 'usertext_timestamp',
02278                         'un_user_id'            => 'user_id',
02279                         'un_user_ip'            => 'user_ip',
02280                 );
02281 
02282                 if ( isset( $renamed[$index] ) ) {
02283                         return $renamed[$index];
02284                 } else {
02285                         return $index;
02286                 }
02287         }
02288 
02297         public function addQuotes( $s ) {
02298                 if ( $s === null ) {
02299                         return 'NULL';
02300                 } else {
02301                         # This will also quote numeric values. This should be harmless,
02302                         # and protects against weird problems that occur when they really
02303                         # _are_ strings such as article titles and string->number->string
02304                         # conversion is not 1:1.
02305                         return "'" . $this->strencode( $s ) . "'";
02306                 }
02307         }
02308 
02319         public function addIdentifierQuotes( $s ) {
02320                 return '"' . str_replace( '"', '""', $s ) . '"';
02321         }
02322 
02331         public function isQuotedIdentifier( $name ) {
02332                 return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02333         }
02334 
02339         protected function escapeLikeInternal( $s ) {
02340                 $s = str_replace( '\\', '\\\\', $s );
02341                 $s = $this->strencode( $s );
02342                 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
02343 
02344                 return $s;
02345         }
02346 
02359         public function buildLike() {
02360                 $params = func_get_args();
02361 
02362                 if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02363                         $params = $params[0];
02364                 }
02365 
02366                 $s = '';
02367 
02368                 foreach ( $params as $value ) {
02369                         if ( $value instanceof LikeMatch ) {
02370                                 $s .= $value->toString();
02371                         } else {
02372                                 $s .= $this->escapeLikeInternal( $value );
02373                         }
02374                 }
02375 
02376                 return " LIKE '" . $s . "' ";
02377         }
02378 
02384         public function anyChar() {
02385                 return new LikeMatch( '_' );
02386         }
02387 
02393         public function anyString() {
02394                 return new LikeMatch( '%' );
02395         }
02396 
02408         public function nextSequenceValue( $seqName ) {
02409                 return null;
02410         }
02411 
02422         public function useIndexClause( $index ) {
02423                 return '';
02424         }
02425 
02448         public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
02449                 $quotedTable = $this->tableName( $table );
02450 
02451                 if ( count( $rows ) == 0 ) {
02452                         return;
02453                 }
02454 
02455                 # Single row case
02456                 if ( !is_array( reset( $rows ) ) ) {
02457                         $rows = array( $rows );
02458                 }
02459 
02460                 foreach( $rows as $row ) {
02461                         # Delete rows which collide
02462                         if ( $uniqueIndexes ) {
02463                                 $sql = "DELETE FROM $quotedTable WHERE ";
02464                                 $first = true;
02465                                 foreach ( $uniqueIndexes as $index ) {
02466                                         if ( $first ) {
02467                                                 $first = false;
02468                                                 $sql .= '( ';
02469                                         } else {
02470                                                 $sql .= ' ) OR ( ';
02471                                         }
02472                                         if ( is_array( $index ) ) {
02473                                                 $first2 = true;
02474                                                 foreach ( $index as $col ) {
02475                                                         if ( $first2 ) {
02476                                                                 $first2 = false;
02477                                                         } else {
02478                                                                 $sql .= ' AND ';
02479                                                         }
02480                                                         $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02481                                                 }
02482                                         } else {
02483                                                 $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02484                                         }
02485                                 }
02486                                 $sql .= ' )';
02487                                 $this->query( $sql, $fname );
02488                         }
02489 
02490                         # Now insert the row
02491                         $this->insert( $table, $row );
02492                 }
02493         }
02494 
02505         protected function nativeReplace( $table, $rows, $fname ) {
02506                 $table = $this->tableName( $table );
02507 
02508                 # Single row case
02509                 if ( !is_array( reset( $rows ) ) ) {
02510                         $rows = array( $rows );
02511                 }
02512 
02513                 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02514                 $first = true;
02515 
02516                 foreach ( $rows as $row ) {
02517                         if ( $first ) {
02518                                 $first = false;
02519                         } else {
02520                                 $sql .= ',';
02521                         }
02522 
02523                         $sql .= '(' . $this->makeList( $row ) . ')';
02524                 }
02525 
02526                 return $this->query( $sql, $fname );
02527         }
02528 
02550         public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02551                 $fname = 'DatabaseBase::deleteJoin' )
02552         {
02553                 if ( !$conds ) {
02554                         throw new DBUnexpectedError( $this,
02555                                 'DatabaseBase::deleteJoin() called with empty $conds' );
02556                 }
02557 
02558                 $delTable = $this->tableName( $delTable );
02559                 $joinTable = $this->tableName( $joinTable );
02560                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02561                 if ( $conds != '*' ) {
02562                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02563                 }
02564                 $sql .= ')';
02565 
02566                 $this->query( $sql, $fname );
02567         }
02568 
02577         public function textFieldSize( $table, $field ) {
02578                 $table = $this->tableName( $table );
02579                 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02580                 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02581                 $row = $this->fetchObject( $res );
02582 
02583                 $m = array();
02584 
02585                 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02586                         $size = $m[1];
02587                 } else {
02588                         $size = -1;
02589                 }
02590 
02591                 return $size;
02592         }
02593 
02602         public function lowPriorityOption() {
02603                 return '';
02604         }
02605 
02617         public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
02618                 if ( !$conds ) {
02619                         throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02620                 }
02621 
02622                 $table = $this->tableName( $table );
02623                 $sql = "DELETE FROM $table";
02624 
02625                 if ( $conds != '*' ) {
02626                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
02627                 }
02628 
02629                 return $this->query( $sql, $fname );
02630         }
02631 
02658         public function insertSelect( $destTable, $srcTable, $varMap, $conds,
02659                 $fname = 'DatabaseBase::insertSelect',
02660                 $insertOptions = array(), $selectOptions = array() )
02661         {
02662                 $destTable = $this->tableName( $destTable );
02663 
02664                 if ( is_array( $insertOptions ) ) {
02665                         $insertOptions = implode( ' ', $insertOptions );
02666                 }
02667 
02668                 if ( !is_array( $selectOptions ) ) {
02669                         $selectOptions = array( $selectOptions );
02670                 }
02671 
02672                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
02673 
02674                 if ( is_array( $srcTable ) ) {
02675                         $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
02676                 } else {
02677                         $srcTable = $this->tableName( $srcTable );
02678                 }
02679 
02680                 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
02681                         " SELECT $startOpts " . implode( ',', $varMap ) .
02682                         " FROM $srcTable $useIndex ";
02683 
02684                 if ( $conds != '*' ) {
02685                         if ( is_array( $conds ) ) {
02686                                 $conds = $this->makeList( $conds, LIST_AND );
02687                         }
02688                         $sql .= " WHERE $conds";
02689                 }
02690 
02691                 $sql .= " $tailOpts";
02692 
02693                 return $this->query( $sql, $fname );
02694         }
02695 
02716         public function limitResult( $sql, $limit, $offset = false ) {
02717                 if ( !is_numeric( $limit ) ) {
02718                         throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
02719                 }
02720                 return "$sql LIMIT "
02721                         . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
02722                         . "{$limit} ";
02723         }
02724 
02730         public function unionSupportsOrderAndLimit() {
02731                 return true; // True for almost every DB supported
02732         }
02733 
02742         public function unionQueries( $sqls, $all ) {
02743                 $glue = $all ? ') UNION ALL (' : ') UNION (';
02744                 return '(' . implode( $glue, $sqls ) . ')';
02745         }
02746 
02756         public function conditional( $cond, $trueVal, $falseVal ) {
02757                 if ( is_array( $cond ) ) {
02758                         $cond = $this->makeList( $cond, LIST_AND );
02759                 }
02760                 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
02761         }
02762 
02773         public function strreplace( $orig, $old, $new ) {
02774                 return "REPLACE({$orig}, {$old}, {$new})";
02775         }
02776 
02783         public function getServerUptime() {
02784                 return 0;
02785         }
02786 
02793         public function wasDeadlock() {
02794                 return false;
02795         }
02796 
02803         public function wasLockTimeout() {
02804                 return false;
02805         }
02806 
02814         public function wasErrorReissuable() {
02815                 return false;
02816         }
02817 
02824         public function wasReadOnlyError() {
02825                 return false;
02826         }
02827 
02846         public function deadlockLoop() {
02847                 $this->begin( __METHOD__ );
02848                 $args = func_get_args();
02849                 $function = array_shift( $args );
02850                 $oldIgnore = $this->ignoreErrors( true );
02851                 $tries = DEADLOCK_TRIES;
02852 
02853                 if ( is_array( $function ) ) {
02854                         $fname = $function[0];
02855                 } else {
02856                         $fname = $function;
02857                 }
02858 
02859                 do {
02860                         $retVal = call_user_func_array( $function, $args );
02861                         $error = $this->lastError();
02862                         $errno = $this->lastErrno();
02863                         $sql = $this->lastQuery();
02864 
02865                         if ( $errno ) {
02866                                 if ( $this->wasDeadlock() ) {
02867                                         # Retry
02868                                         usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
02869                                 } else {
02870                                         $this->reportQueryError( $error, $errno, $sql, $fname );
02871                                 }
02872                         }
02873                 } while ( $this->wasDeadlock() && --$tries > 0 );
02874 
02875                 $this->ignoreErrors( $oldIgnore );
02876 
02877                 if ( $tries <= 0 ) {
02878                         $this->rollback( __METHOD__ );
02879                         $this->reportQueryError( $error, $errno, $sql, $fname );
02880                         return false;
02881                 } else {
02882                         $this->commit( __METHOD__ );
02883                         return $retVal;
02884                 }
02885         }
02886 
02898         public function masterPosWait( DBMasterPos $pos, $timeout ) {
02899                 wfProfileIn( __METHOD__ );
02900 
02901                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02902                         $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
02903 
02904                         if ( $wait > $timeout * 1e6 ) {
02905                                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
02906                                 wfProfileOut( __METHOD__ );
02907                                 return -1;
02908                         } elseif ( $wait > 0 ) {
02909                                 wfDebug( "Fake slave waiting $wait us\n" );
02910                                 usleep( $wait );
02911                                 wfProfileOut( __METHOD__ );
02912                                 return 1;
02913                         } else {
02914                                 wfDebug( "Fake slave up to date ($wait us)\n" );
02915                                 wfProfileOut( __METHOD__ );
02916                                 return 0;
02917                         }
02918                 }
02919 
02920                 wfProfileOut( __METHOD__ );
02921 
02922                 # Real waits are implemented in the subclass.
02923                 return 0;
02924         }
02925 
02931         public function getSlavePos() {
02932                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02933                         $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
02934                         wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
02935                         return $pos;
02936                 } else {
02937                         # Stub
02938                         return false;
02939                 }
02940         }
02941 
02947         public function getMasterPos() {
02948                 if ( $this->mFakeMaster ) {
02949                         return new MySQLMasterPos( 'fake', microtime( true ) );
02950                 } else {
02951                         return false;
02952                 }
02953         }
02954 
02966         final public function onTransactionIdle( Closure $callback ) {
02967                 if ( $this->mTrxLevel ) {
02968                         $this->mTrxIdleCallbacks[] = $callback;
02969                 } else {
02970                         $callback();
02971                 }
02972         }
02973 
02979         protected function runOnTransactionIdleCallbacks() {
02980                 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
02981 
02982                 $e = null; // last exception
02983                 do { // callbacks may add callbacks :)
02984                         $callbacks = $this->mTrxIdleCallbacks;
02985                         $this->mTrxIdleCallbacks = array(); // recursion guard
02986                         foreach ( $callbacks as $callback ) {
02987                                 try {
02988                                         $this->clearFlag( DBO_TRX ); // make each query its own transaction
02989                                         $callback();
02990                                         $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
02991                                 } catch ( Exception $e ) {}
02992                         }
02993                 } while ( count( $this->mTrxIdleCallbacks ) );
02994 
02995                 if ( $e instanceof Exception ) {
02996                         throw $e; // re-throw any last exception
02997                 }
02998         }
02999 
03012         final public function begin( $fname = 'DatabaseBase::begin' ) {
03013                 global $wgDebugDBTransactions;
03014 
03015                 if ( $this->mTrxLevel ) { // implicit commit
03016                         if ( !$this->mTrxAutomatic ) {
03017                                 // We want to warn about inadvertently nested begin/commit pairs, but not about
03018                                 // auto-committing implicit transactions that were started by query() via DBO_TRX
03019                                 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
03020                                         " performing implicit commit!";
03021                                 wfWarn( $msg );
03022                                 wfLogDBError( $msg );
03023                         } else {
03024                                 // if the transaction was automatic and has done write operations,
03025                                 // log it if $wgDebugDBTransactions is enabled.
03026                                 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
03027                                         wfDebug( "$fname: Automatic transaction with writes in progress" .
03028                                                 " (from {$this->mTrxFname}), performing implicit commit!\n" );
03029                                 }
03030                         }
03031 
03032                         $this->doCommit( $fname );
03033                         $this->runOnTransactionIdleCallbacks();
03034                 }
03035 
03036                 $this->doBegin( $fname );
03037                 $this->mTrxFname = $fname;
03038                 $this->mTrxDoneWrites = false;
03039                 $this->mTrxAutomatic = false;
03040         }
03041 
03048         protected function doBegin( $fname ) {
03049                 $this->query( 'BEGIN', $fname );
03050                 $this->mTrxLevel = 1;
03051         }
03052 
03065         final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
03066                 if ( $flush != 'flush' ) {
03067                         if ( !$this->mTrxLevel ) {
03068                                 wfWarn( "$fname: No transaction to commit, something got out of sync!" );
03069                         } elseif( $this->mTrxAutomatic ) {
03070                                 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
03071                         }
03072                 } else {
03073                         if ( !$this->mTrxLevel ) {
03074                                 return; // nothing to do
03075                         } elseif( !$this->mTrxAutomatic ) {
03076                                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03077                         }
03078                 }
03079 
03080                 $this->doCommit( $fname );
03081                 $this->runOnTransactionIdleCallbacks();
03082         }
03083 
03090         protected function doCommit( $fname ) {
03091                 if ( $this->mTrxLevel ) {
03092                         $this->query( 'COMMIT', $fname );
03093                         $this->mTrxLevel = 0;
03094                 }
03095         }
03096 
03105         final public function rollback( $fname = 'DatabaseBase::rollback' ) {
03106                 if ( !$this->mTrxLevel ) {
03107                         wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
03108                 }
03109                 $this->doRollback( $fname );
03110                 $this->mTrxIdleCallbacks = array(); // cancel
03111         }
03112 
03119         protected function doRollback( $fname ) {
03120                 if ( $this->mTrxLevel ) {
03121                         $this->query( 'ROLLBACK', $fname, true );
03122                         $this->mTrxLevel = 0;
03123                 }
03124         }
03125 
03141         public function duplicateTableStructure( $oldName, $newName, $temporary = false,
03142                 $fname = 'DatabaseBase::duplicateTableStructure' )
03143         {
03144                 throw new MWException(
03145                         'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
03146         }
03147 
03155         function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
03156                 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
03157         }
03158 
03170         public function timestamp( $ts = 0 ) {
03171                 return wfTimestamp( TS_MW, $ts );
03172         }
03173 
03187         public function timestampOrNull( $ts = null ) {
03188                 if ( is_null( $ts ) ) {
03189                         return null;
03190                 } else {
03191                         return $this->timestamp( $ts );
03192                 }
03193         }
03194 
03210         public function resultObject( $result ) {
03211                 if ( empty( $result ) ) {
03212                         return false;
03213                 } elseif ( $result instanceof ResultWrapper ) {
03214                         return $result;
03215                 } elseif ( $result === true ) {
03216                         // Successful write query
03217                         return $result;
03218                 } else {
03219                         return new ResultWrapper( $this, $result );
03220                 }
03221         }
03222 
03228         public function ping() {
03229                 # Stub.  Not essential to override.
03230                 return true;
03231         }
03232 
03242         public function getLag() {
03243                 return intval( $this->mFakeSlaveLag );
03244         }
03245 
03251         function maxListLen() {
03252                 return 0;
03253         }
03254 
03263         public function encodeBlob( $b ) {
03264                 return $b;
03265         }
03266 
03274         public function decodeBlob( $b ) {
03275                 return $b;
03276         }
03277 
03288         public function setSessionOptions( array $options ) {}
03289 
03306         public function sourceFile(
03307                 $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
03308         ) {
03309                 wfSuppressWarnings();
03310                 $fp = fopen( $filename, 'r' );
03311                 wfRestoreWarnings();
03312 
03313                 if ( false === $fp ) {
03314                         throw new MWException( "Could not open \"{$filename}\".\n" );
03315                 }
03316 
03317                 if ( !$fname ) {
03318                         $fname = __METHOD__ . "( $filename )";
03319                 }
03320 
03321                 try {
03322                         $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
03323                 }
03324                 catch ( MWException $e ) {
03325                         fclose( $fp );
03326                         throw $e;
03327                 }
03328 
03329                 fclose( $fp );
03330 
03331                 return $error;
03332         }
03333 
03342         public function patchPath( $patch ) {
03343                 global $IP;
03344 
03345                 $dbType = $this->getType();
03346                 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03347                         return "$IP/maintenance/$dbType/archives/$patch";
03348                 } else {
03349                         return "$IP/maintenance/archives/$patch";
03350                 }
03351         }
03352 
03360         public function setSchemaVars( $vars ) {
03361                 $this->mSchemaVars = $vars;
03362         }
03363 
03377         public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03378                 $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
03379         {
03380                 $cmd = '';
03381 
03382                 while ( !feof( $fp ) ) {
03383                         if ( $lineCallback ) {
03384                                 call_user_func( $lineCallback );
03385                         }
03386 
03387                         $line = trim( fgets( $fp ) );
03388 
03389                         if ( $line == '' ) {
03390                                 continue;
03391                         }
03392 
03393                         if ( '-' == $line[0] && '-' == $line[1] ) {
03394                                 continue;
03395                         }
03396 
03397                         if ( $cmd != '' ) {
03398                                 $cmd .= ' ';
03399                         }
03400 
03401                         $done = $this->streamStatementEnd( $cmd, $line );
03402 
03403                         $cmd .= "$line\n";
03404 
03405                         if ( $done || feof( $fp ) ) {
03406                                 $cmd = $this->replaceVars( $cmd );
03407 
03408                                 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
03409                                         $res = $this->query( $cmd, $fname );
03410 
03411                                         if ( $resultCallback ) {
03412                                                 call_user_func( $resultCallback, $res, $this );
03413                                         }
03414 
03415                                         if ( false === $res ) {
03416                                                 $err = $this->lastError();
03417                                                 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03418                                         }
03419                                 }
03420                                 $cmd = '';
03421                         }
03422                 }
03423 
03424                 return true;
03425         }
03426 
03434         public function streamStatementEnd( &$sql, &$newLine ) {
03435                 if ( $this->delimiter ) {
03436                         $prev = $newLine;
03437                         $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
03438                         if ( $newLine != $prev ) {
03439                                 return true;
03440                         }
03441                 }
03442                 return false;
03443         }
03444 
03462         protected function replaceSchemaVars( $ins ) {
03463                 $vars = $this->getSchemaVars();
03464                 foreach ( $vars as $var => $value ) {
03465                         // replace '{$var}'
03466                         $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
03467                         // replace `{$var}`
03468                         $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
03469                         // replace /*$var*/
03470                         $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
03471                 }
03472                 return $ins;
03473         }
03474 
03482         protected function replaceVars( $ins ) {
03483                 $ins = $this->replaceSchemaVars( $ins );
03484 
03485                 // Table prefixes
03486                 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
03487                         array( $this, 'tableNameCallback' ), $ins );
03488 
03489                 // Index names
03490                 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
03491                         array( $this, 'indexNameCallback' ), $ins );
03492 
03493                 return $ins;
03494         }
03495 
03502         protected function getSchemaVars() {
03503                 if ( $this->mSchemaVars ) {
03504                         return $this->mSchemaVars;
03505                 } else {
03506                         return $this->getDefaultSchemaVars();
03507                 }
03508         }
03509 
03518         protected function getDefaultSchemaVars() {
03519                 return array();
03520         }
03521 
03529         protected function tableNameCallback( $matches ) {
03530                 return $this->tableName( $matches[1] );
03531         }
03532 
03540         protected function indexNameCallback( $matches ) {
03541                 return $this->indexName( $matches[1] );
03542         }
03543 
03552         public function lockIsFree( $lockName, $method ) {
03553                 return true;
03554         }
03555 
03567         public function lock( $lockName, $method, $timeout = 5 ) {
03568                 return true;
03569         }
03570 
03581         public function unlock( $lockName, $method ) {
03582                 return true;
03583         }
03584 
03595         public function lockTables( $read, $write, $method, $lowPriority = true ) {
03596                 return true;
03597         }
03598 
03606         public function unlockTables( $method ) {
03607                 return true;
03608         }
03609 
03617         public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
03618                 if( !$this->tableExists( $tableName, $fName ) ) {
03619                         return false;
03620                 }
03621                 $sql = "DROP TABLE " . $this->tableName( $tableName );
03622                 if( $this->cascadingDeletes() ) {
03623                         $sql .= " CASCADE";
03624                 }
03625                 return $this->query( $sql, $fName );
03626         }
03627 
03634         public function getSearchEngine() {
03635                 return 'SearchEngineDummy';
03636         }
03637 
03645         public function getInfinity() {
03646                 return 'infinity';
03647         }
03648 
03655         public function encodeExpiry( $expiry ) {
03656                 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
03657                         ? $this->getInfinity()
03658                         : $this->timestamp( $expiry );
03659         }
03660 
03668         public function decodeExpiry( $expiry, $format = TS_MW ) {
03669                 return ( $expiry == '' || $expiry == $this->getInfinity() )
03670                         ? 'infinity'
03671                         : wfTimestamp( $format, $expiry );
03672         }
03673 
03683         public function setBigSelects( $value = true ) {
03684                 // no-op
03685         }
03686 
03690         public function __toString() {
03691                 return (string)$this->mConn;
03692         }
03693 
03694         public function __destruct() {
03695                 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
03696                         trigger_error( "Transaction idle callbacks still pending." );
03697                 }
03698         }
03699 }