MediaWiki  master
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 
00070         function fetchObject( $res );
00071 
00080         function fetchRow( $res );
00081 
00088         function numRows( $res );
00089 
00097         function numFields( $res );
00098 
00107         function fieldName( $res, $n );
00108 
00121         function insertId();
00122 
00130         function dataSeek( $res, $row );
00131 
00138         function lastErrno();
00139 
00146         function lastError();
00147 
00157         function fieldInfo( $table, $field );
00158 
00166         function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
00167 
00174         function affectedRows();
00175 
00182         function strencode( $s );
00183 
00192         static function getSoftwareLink();
00193 
00200         function getServerVersion();
00201 
00209         function getServerInfo();
00210 }
00211 
00216 abstract class DatabaseBase implements DatabaseType {
00217 
00218 # ------------------------------------------------------------------------------
00219 # Variables
00220 # ------------------------------------------------------------------------------
00221 
00222         protected $mLastQuery = '';
00223         protected $mDoneWrites = false;
00224         protected $mPHPError = false;
00225 
00226         protected $mServer, $mUser, $mPassword, $mDBname;
00227 
00228         protected $mConn = null;
00229         protected $mOpened = false;
00230 
00235         protected $trxIdleCallbacks = array();
00236 
00237         protected $mTablePrefix;
00238         protected $mFlags;
00239         protected $mTrxLevel = 0;
00240         protected $mErrorCount = 0;
00241         protected $mLBInfo = array();
00242         protected $mFakeSlaveLag = null, $mFakeMaster = false;
00243         protected $mDefaultBigSelects = null;
00244         protected $mSchemaVars = false;
00245 
00246         protected $preparedArgs;
00247 
00248         protected $htmlErrors;
00249 
00250         protected $delimiter = ';';
00251 
00259         private $mTrxFname = null;
00260 
00267         private $mTrxDoneWrites = false;
00268 
00275         private $mTrxAutomatic = false;
00276 
00277 # ------------------------------------------------------------------------------
00278 # Accessors
00279 # ------------------------------------------------------------------------------
00280         # These optionally set a variable and return the previous state
00281 
00289         public function getServerInfo() {
00290                 return $this->getServerVersion();
00291         }
00292 
00296         public function getDelimiter() {
00297                 return $this->delimiter;
00298         }
00299 
00309         public function debug( $debug = null ) {
00310                 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00311         }
00312 
00335         public function bufferResults( $buffer = null ) {
00336                 if ( is_null( $buffer ) ) {
00337                         return !(bool)( $this->mFlags & DBO_NOBUFFER );
00338                 } else {
00339                         return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00340                 }
00341         }
00342 
00354         public function ignoreErrors( $ignoreErrors = null ) {
00355                 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00356         }
00357 
00367         public function trxLevel( $level = null ) {
00368                 return wfSetVar( $this->mTrxLevel, $level );
00369         }
00370 
00376         public function errorCount( $count = null ) {
00377                 return wfSetVar( $this->mErrorCount, $count );
00378         }
00379 
00385         public function tablePrefix( $prefix = null ) {
00386                 return wfSetVar( $this->mTablePrefix, $prefix );
00387         }
00388 
00398         public function getLBInfo( $name = null ) {
00399                 if ( is_null( $name ) ) {
00400                         return $this->mLBInfo;
00401                 } else {
00402                         if ( array_key_exists( $name, $this->mLBInfo ) ) {
00403                                 return $this->mLBInfo[$name];
00404                         } else {
00405                                 return null;
00406                         }
00407                 }
00408         }
00409 
00418         public function setLBInfo( $name, $value = null ) {
00419                 if ( is_null( $value ) ) {
00420                         $this->mLBInfo = $name;
00421                 } else {
00422                         $this->mLBInfo[$name] = $value;
00423                 }
00424         }
00425 
00431         public function setFakeSlaveLag( $lag ) {
00432                 $this->mFakeSlaveLag = $lag;
00433         }
00434 
00440         public function setFakeMaster( $enabled = true ) {
00441                 $this->mFakeMaster = $enabled;
00442         }
00443 
00449         public function cascadingDeletes() {
00450                 return false;
00451         }
00452 
00458         public function cleanupTriggers() {
00459                 return false;
00460         }
00461 
00468         public function strictIPs() {
00469                 return false;
00470         }
00471 
00477         public function realTimestamps() {
00478                 return false;
00479         }
00480 
00486         public function implicitGroupby() {
00487                 return true;
00488         }
00489 
00496         public function implicitOrderby() {
00497                 return true;
00498         }
00499 
00506         public function searchableIPs() {
00507                 return false;
00508         }
00509 
00515         public function functionalIndexes() {
00516                 return false;
00517         }
00518 
00523         public function lastQuery() {
00524                 return $this->mLastQuery;
00525         }
00526 
00533         public function doneWrites() {
00534                 return $this->mDoneWrites;
00535         }
00536 
00541         public function isOpen() {
00542                 return $this->mOpened;
00543         }
00544 
00557         public function setFlag( $flag ) {
00558                 global $wgDebugDBTransactions;
00559                 $this->mFlags |= $flag;
00560                 if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
00561                         wfDebug("Implicit transactions are now  disabled.\n");
00562                 }
00563         }
00564 
00570         public function clearFlag( $flag ) {
00571                 global $wgDebugDBTransactions;
00572                 $this->mFlags &= ~$flag;
00573                 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00574                         wfDebug("Implicit transactions are now disabled.\n");
00575                 }
00576         }
00577 
00584         public function getFlag( $flag ) {
00585                 return !!( $this->mFlags & $flag );
00586         }
00587 
00595         public function getProperty( $name ) {
00596                 return $this->$name;
00597         }
00598 
00602         public function getWikiID() {
00603                 if ( $this->mTablePrefix ) {
00604                         return "{$this->mDBname}-{$this->mTablePrefix}";
00605                 } else {
00606                         return $this->mDBname;
00607                 }
00608         }
00609 
00615         public function getSchemaPath() {
00616                 global $IP;
00617                 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
00618                         return "$IP/maintenance/" . $this->getType() . "/tables.sql";
00619                 } else {
00620                         return "$IP/maintenance/tables.sql";
00621                 }
00622         }
00623 
00624 # ------------------------------------------------------------------------------
00625 # Other functions
00626 # ------------------------------------------------------------------------------
00627 
00637         function __construct( $server = false, $user = false, $password = false, $dbName = false,
00638                 $flags = 0, $tablePrefix = 'get from global'
00639         ) {
00640                 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
00641 
00642                 $this->mFlags = $flags;
00643 
00644                 if ( $this->mFlags & DBO_DEFAULT ) {
00645                         if ( $wgCommandLineMode ) {
00646                                 $this->mFlags &= ~DBO_TRX;
00647                                 if ( $wgDebugDBTransactions ) {
00648                                         wfDebug("Implicit transaction open disabled.\n");
00649                                 }
00650                         } else {
00651                                 $this->mFlags |= DBO_TRX;
00652                                 if ( $wgDebugDBTransactions ) {
00653                                         wfDebug("Implicit transaction open enabled.\n");
00654                                 }
00655                         }
00656                 }
00657 
00659                 if ( $tablePrefix == 'get from global' ) {
00660                         $this->mTablePrefix = $wgDBprefix;
00661                 } else {
00662                         $this->mTablePrefix = $tablePrefix;
00663                 }
00664 
00665                 if ( $user ) {
00666                         $this->open( $server, $user, $password, $dbName );
00667                 }
00668         }
00669 
00675         public function __sleep() {
00676                 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
00677         }
00678 
00701         public final static function factory( $dbType, $p = array() ) {
00702                 $canonicalDBTypes = array(
00703                         'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
00704                 );
00705                 $dbType = strtolower( $dbType );
00706                 $class = 'Database' . ucfirst( $dbType );
00707 
00708                 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
00709                         return new $class(
00710                                 isset( $p['host'] ) ? $p['host'] : false,
00711                                 isset( $p['user'] ) ? $p['user'] : false,
00712                                 isset( $p['password'] ) ? $p['password'] : false,
00713                                 isset( $p['dbname'] ) ? $p['dbname'] : false,
00714                                 isset( $p['flags'] ) ? $p['flags'] : 0,
00715                                 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
00716                         );
00717                 } else {
00718                         return null;
00719                 }
00720         }
00721 
00722         protected function installErrorHandler() {
00723                 $this->mPHPError = false;
00724                 $this->htmlErrors = ini_set( 'html_errors', '0' );
00725                 set_error_handler( array( $this, 'connectionErrorHandler' ) );
00726         }
00727 
00731         protected function restoreErrorHandler() {
00732                 restore_error_handler();
00733                 if ( $this->htmlErrors !== false ) {
00734                         ini_set( 'html_errors', $this->htmlErrors );
00735                 }
00736                 if ( $this->mPHPError ) {
00737                         $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00738                         $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
00739                         return $error;
00740                 } else {
00741                         return false;
00742                 }
00743         }
00744 
00749         protected function connectionErrorHandler( $errno,  $errstr ) {
00750                 $this->mPHPError = $errstr;
00751         }
00752 
00759         public function close() {
00760                 $this->mOpened = false;
00761                 if ( $this->mConn ) {
00762                         if ( $this->trxLevel() ) {
00763                                 if ( !$this->mTrxAutomatic ) {
00764                                         wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
00765                                                 " performing implicit commit before closing connection!" );
00766                                 }
00767 
00768                                 $this->commit( __METHOD__, 'flush' );
00769                         }
00770 
00771                         $ret = $this->closeConnection();
00772                         $this->mConn = false;
00773                         return $ret;
00774                 } else {
00775                         return true;
00776                 }
00777         }
00778 
00784         protected abstract function closeConnection();
00785 
00790         function reportConnectionError( $error = 'Unknown error' ) {
00791                 $myError = $this->lastError();
00792                 if ( $myError ) {
00793                         $error = $myError;
00794                 }
00795 
00796                 # New method
00797                 throw new DBConnectionError( $this, $error );
00798         }
00799 
00806         protected abstract function doQuery( $sql );
00807 
00816         public function isWriteQuery( $sql ) {
00817                 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
00818         }
00819 
00842         public function query( $sql, $fname = '', $tempIgnore = false ) {
00843                 $isMaster = !is_null( $this->getLBInfo( 'master' ) );
00844                 if ( !Profiler::instance()->isStub() ) {
00845                         # generalizeSQL will probably cut down the query to reasonable
00846                         # logging size most of the time. The substr is really just a sanity check.
00847 
00848                         if ( $isMaster ) {
00849                                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00850                                 $totalProf = 'DatabaseBase::query-master';
00851                         } else {
00852                                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00853                                 $totalProf = 'DatabaseBase::query';
00854                         }
00855 
00856                         wfProfileIn( $totalProf );
00857                         wfProfileIn( $queryProf );
00858                 }
00859 
00860                 $this->mLastQuery = $sql;
00861                 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
00862                         # Set a flag indicating that writes have been done
00863                         wfDebug( __METHOD__ . ": Writes done: $sql\n" );
00864                         $this->mDoneWrites = true;
00865                 }
00866 
00867                 # Add a comment for easy SHOW PROCESSLIST interpretation
00868                 global $wgUser;
00869                 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
00870                         $userName = $wgUser->getName();
00871                         if ( mb_strlen( $userName ) > 15 ) {
00872                                 $userName = mb_substr( $userName, 0, 15 ) . '...';
00873                         }
00874                         $userName = str_replace( '/', '', $userName );
00875                 } else {
00876                         $userName = '';
00877                 }
00878                 $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
00879 
00880                 # If DBO_TRX is set, start a transaction
00881                 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
00882                         $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
00883                 {
00884                         # Avoid establishing transactions for SHOW and SET statements too -
00885                         # that would delay transaction initializations to once connection
00886                         # is really used by application
00887                         $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
00888                         if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
00889                                 global $wgDebugDBTransactions;
00890                                 if ( $wgDebugDBTransactions ) {
00891                                         wfDebug("Implicit transaction start.\n");
00892                                 }
00893                                 $this->begin( __METHOD__ . " ($fname)" );
00894                                 $this->mTrxAutomatic = true;
00895                         }
00896                 }
00897 
00898                 # Keep track of whether the transaction has write queries pending
00899                 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
00900                         $this->mTrxDoneWrites = true;
00901                 }
00902 
00903                 if ( $this->debug() ) {
00904                         static $cnt = 0;
00905 
00906                         $cnt++;
00907                         $sqlx = substr( $commentedSql, 0, 500 );
00908                         $sqlx = strtr( $sqlx, "\t\n", '  ' );
00909 
00910                         $master = $isMaster ? 'master' : 'slave';
00911                         wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
00912                 }
00913 
00914                 if ( istainted( $sql ) & TC_MYSQL ) {
00915                         throw new MWException( 'Tainted query found' );
00916                 }
00917 
00918                 $queryId = MWDebug::query( $sql, $fname, $isMaster );
00919 
00920                 # Do the query and handle errors
00921                 $ret = $this->doQuery( $commentedSql );
00922 
00923                 MWDebug::queryTime( $queryId );
00924 
00925                 # Try reconnecting if the connection was lost
00926                 if ( false === $ret && $this->wasErrorReissuable() ) {
00927                         # Transaction is gone, like it or not
00928                         $this->mTrxLevel = 0;
00929                         $this->trxIdleCallbacks = array(); // cancel
00930                         wfDebug( "Connection lost, reconnecting...\n" );
00931 
00932                         if ( $this->ping() ) {
00933                                 wfDebug( "Reconnected\n" );
00934                                 $sqlx = substr( $commentedSql, 0, 500 );
00935                                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
00936                                 global $wgRequestTime;
00937                                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
00938                                 if ( $elapsed < 300 ) {
00939                                         # Not a database error to lose a transaction after a minute or two
00940                                         wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
00941                                 }
00942                                 $ret = $this->doQuery( $commentedSql );
00943                         } else {
00944                                 wfDebug( "Failed\n" );
00945                         }
00946                 }
00947 
00948                 if ( false === $ret ) {
00949                         $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
00950                 }
00951 
00952                 if ( !Profiler::instance()->isStub() ) {
00953                         wfProfileOut( $queryProf );
00954                         wfProfileOut( $totalProf );
00955                 }
00956 
00957                 return $this->resultObject( $ret );
00958         }
00959 
00971         public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
00972                 # Ignore errors during error handling to avoid infinite recursion
00973                 $ignore = $this->ignoreErrors( true );
00974                 ++$this->mErrorCount;
00975 
00976                 if ( $ignore || $tempIgnore ) {
00977                         wfDebug( "SQL ERROR (ignored): $error\n" );
00978                         $this->ignoreErrors( $ignore );
00979                 } else {
00980                         $sql1line = str_replace( "\n", "\\n", $sql );
00981                         wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
00982                         wfDebug( "SQL ERROR: " . $error . "\n" );
00983                         throw new DBQueryError( $this, $error, $errno, $sql, $fname );
00984                 }
00985         }
00986 
01001         protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
01002                 /* MySQL doesn't support prepared statements (yet), so just
01003                    pack up the query for reference. We'll manually replace
01004                    the bits later. */
01005                 return array( 'query' => $sql, 'func' => $func );
01006         }
01007 
01012         protected function freePrepared( $prepared ) {
01013                 /* No-op by default */
01014         }
01015 
01023         public function execute( $prepared, $args = null ) {
01024                 if ( !is_array( $args ) ) {
01025                         # Pull the var args
01026                         $args = func_get_args();
01027                         array_shift( $args );
01028                 }
01029 
01030                 $sql = $this->fillPrepared( $prepared['query'], $args );
01031 
01032                 return $this->query( $sql, $prepared['func'] );
01033         }
01034 
01042         public function fillPrepared( $preparedQuery, $args ) {
01043                 reset( $args );
01044                 $this->preparedArgs =& $args;
01045 
01046                 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01047                         array( &$this, 'fillPreparedArg' ), $preparedQuery );
01048         }
01049 
01059         protected function fillPreparedArg( $matches ) {
01060                 switch( $matches[1] ) {
01061                         case '\\?': return '?';
01062                         case '\\!': return '!';
01063                         case '\\&': return '&';
01064                 }
01065 
01066                 list( /* $n */ , $arg ) = each( $this->preparedArgs );
01067 
01068                 switch( $matches[1] ) {
01069                         case '?': return $this->addQuotes( $arg );
01070                         case '!': return $arg;
01071                         case '&':
01072                                 # return $this->addQuotes( file_get_contents( $arg ) );
01073                                 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
01074                         default:
01075                                 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
01076                 }
01077         }
01078 
01086         public function freeResult( $res ) {}
01087 
01105         public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
01106                 $options = array() )
01107         {
01108                 if ( !is_array( $options ) ) {
01109                         $options = array( $options );
01110                 }
01111 
01112                 $options['LIMIT'] = 1;
01113 
01114                 $res = $this->select( $table, $var, $cond, $fname, $options );
01115 
01116                 if ( $res === false || !$this->numRows( $res ) ) {
01117                         return false;
01118                 }
01119 
01120                 $row = $this->fetchRow( $res );
01121 
01122                 if ( $row !== false ) {
01123                         return reset( $row );
01124                 } else {
01125                         return false;
01126                 }
01127         }
01128 
01138         public function makeSelectOptions( $options ) {
01139                 $preLimitTail = $postLimitTail = '';
01140                 $startOpts = '';
01141 
01142                 $noKeyOptions = array();
01143 
01144                 foreach ( $options as $key => $option ) {
01145                         if ( is_numeric( $key ) ) {
01146                                 $noKeyOptions[$option] = true;
01147                         }
01148                 }
01149 
01150                 if ( isset( $options['GROUP BY'] ) ) {
01151                         $gb = is_array( $options['GROUP BY'] )
01152                                 ? implode( ',', $options['GROUP BY'] )
01153                                 : $options['GROUP BY'];
01154                         $preLimitTail .= " GROUP BY {$gb}";
01155                 }
01156 
01157                 if ( isset( $options['HAVING'] ) ) {
01158                         $having = is_array( $options['HAVING'] )
01159                                 ? $this->makeList( $options['HAVING'], LIST_AND )
01160                                 : $options['HAVING'];
01161                         $preLimitTail .= " HAVING {$having}";
01162                 }
01163 
01164                 if ( isset( $options['ORDER BY'] ) ) {
01165                         $ob = is_array( $options['ORDER BY'] )
01166                                 ? implode( ',', $options['ORDER BY'] )
01167                                 : $options['ORDER BY'];
01168                         $preLimitTail .= " ORDER BY {$ob}";
01169                 }
01170 
01171                 // if (isset($options['LIMIT'])) {
01172                 //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
01173                 //              isset($options['OFFSET']) ? $options['OFFSET']
01174                 //              : false);
01175                 // }
01176 
01177                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01178                         $postLimitTail .= ' FOR UPDATE';
01179                 }
01180 
01181                 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01182                         $postLimitTail .= ' LOCK IN SHARE MODE';
01183                 }
01184 
01185                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01186                         $startOpts .= 'DISTINCT';
01187                 }
01188 
01189                 # Various MySQL extensions
01190                 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01191                         $startOpts .= ' /*! STRAIGHT_JOIN */';
01192                 }
01193 
01194                 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01195                         $startOpts .= ' HIGH_PRIORITY';
01196                 }
01197 
01198                 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01199                         $startOpts .= ' SQL_BIG_RESULT';
01200                 }
01201 
01202                 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01203                         $startOpts .= ' SQL_BUFFER_RESULT';
01204                 }
01205 
01206                 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01207                         $startOpts .= ' SQL_SMALL_RESULT';
01208                 }
01209 
01210                 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01211                         $startOpts .= ' SQL_CALC_FOUND_ROWS';
01212                 }
01213 
01214                 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01215                         $startOpts .= ' SQL_CACHE';
01216                 }
01217 
01218                 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01219                         $startOpts .= ' SQL_NO_CACHE';
01220                 }
01221 
01222                 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
01223                         $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01224                 } else {
01225                         $useIndex = '';
01226                 }
01227 
01228                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01229         }
01230 
01370         public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01371                 $options = array(), $join_conds = array() ) {
01372                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01373 
01374                 return $this->query( $sql, $fname );
01375         }
01376 
01393         public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01394                 $options = array(), $join_conds = array() )
01395         {
01396                 if ( is_array( $vars ) ) {
01397                         $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
01398                 }
01399 
01400                 $options = (array)$options;
01401 
01402                 if ( is_array( $table ) ) {
01403                         $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01404                                 ? $options['USE INDEX']
01405                                 : array();
01406                         if ( count( $join_conds ) || count( $useIndex ) ) {
01407                                 $from = ' FROM ' .
01408                                         $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
01409                         } else {
01410                                 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
01411                         }
01412                 } elseif ( $table != '' ) {
01413                         if ( $table[0] == ' ' ) {
01414                                 $from = ' FROM ' . $table;
01415                         } else {
01416                                 $from = ' FROM ' . $this->tableName( $table );
01417                         }
01418                 } else {
01419                         $from = '';
01420                 }
01421 
01422                 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
01423 
01424                 if ( !empty( $conds ) ) {
01425                         if ( is_array( $conds ) ) {
01426                                 $conds = $this->makeList( $conds, LIST_AND );
01427                         }
01428                         $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01429                 } else {
01430                         $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01431                 }
01432 
01433                 if ( isset( $options['LIMIT'] ) ) {
01434                         $sql = $this->limitResult( $sql, $options['LIMIT'],
01435                                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01436                 }
01437                 $sql = "$sql $postLimitTail";
01438 
01439                 if ( isset( $options['EXPLAIN'] ) ) {
01440                         $sql = 'EXPLAIN ' . $sql;
01441                 }
01442 
01443                 return $sql;
01444         }
01445 
01460         public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
01461                 $options = array(), $join_conds = array() )
01462         {
01463                 $options = (array)$options;
01464                 $options['LIMIT'] = 1;
01465                 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01466 
01467                 if ( $res === false ) {
01468                         return false;
01469                 }
01470 
01471                 if ( !$this->numRows( $res ) ) {
01472                         return false;
01473                 }
01474 
01475                 $obj = $this->fetchObject( $res );
01476 
01477                 return $obj;
01478         }
01479 
01500         public function estimateRowCount( $table, $vars = '*', $conds = '',
01501                 $fname = 'DatabaseBase::estimateRowCount', $options = array() )
01502         {
01503                 $rows = 0;
01504                 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
01505 
01506                 if ( $res ) {
01507                         $row = $this->fetchRow( $res );
01508                         $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01509                 }
01510 
01511                 return $rows;
01512         }
01513 
01522         static function generalizeSQL( $sql ) {
01523                 # This does the same as the regexp below would do, but in such a way
01524                 # as to avoid crashing php on some large strings.
01525                 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
01526 
01527                 $sql = str_replace ( "\\\\", '', $sql );
01528                 $sql = str_replace ( "\\'", '', $sql );
01529                 $sql = str_replace ( "\\\"", '', $sql );
01530                 $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
01531                 $sql = preg_replace ( '/".*"/s', "'X'", $sql );
01532 
01533                 # All newlines, tabs, etc replaced by single space
01534                 $sql = preg_replace ( '/\s+/', ' ', $sql );
01535 
01536                 # All numbers => N
01537                 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
01538 
01539                 return $sql;
01540         }
01541 
01550         public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
01551                 $info = $this->fieldInfo( $table, $field );
01552 
01553                 return (bool)$info;
01554         }
01555 
01567         public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
01568                 $info = $this->indexInfo( $table, $index, $fname );
01569                 if ( is_null( $info ) ) {
01570                         return null;
01571                 } else {
01572                         return $info !== false;
01573                 }
01574         }
01575 
01584         public function tableExists( $table, $fname = __METHOD__ ) {
01585                 $table = $this->tableName( $table );
01586                 $old = $this->ignoreErrors( true );
01587                 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01588                 $this->ignoreErrors( $old );
01589 
01590                 return (bool)$res;
01591         }
01592 
01599         public function fieldType( $res, $index ) {
01600                 if ( $res instanceof ResultWrapper ) {
01601                         $res = $res->result;
01602                 }
01603 
01604                 return mysql_field_type( $res, $index );
01605         }
01606 
01615         public function indexUnique( $table, $index ) {
01616                 $indexInfo = $this->indexInfo( $table, $index );
01617 
01618                 if ( !$indexInfo ) {
01619                         return null;
01620                 }
01621 
01622                 return !$indexInfo[0]->Non_unique;
01623         }
01624 
01631         protected function makeInsertOptions( $options ) {
01632                 return implode( ' ', $options );
01633         }
01634 
01668         public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
01669                 # No rows to insert, easy just return now
01670                 if ( !count( $a ) ) {
01671                         return true;
01672                 }
01673 
01674                 $table = $this->tableName( $table );
01675 
01676                 if ( !is_array( $options ) ) {
01677                         $options = array( $options );
01678                 }
01679 
01680                 $options = $this->makeInsertOptions( $options );
01681 
01682                 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01683                         $multi = true;
01684                         $keys = array_keys( $a[0] );
01685                 } else {
01686                         $multi = false;
01687                         $keys = array_keys( $a );
01688                 }
01689 
01690                 $sql = 'INSERT ' . $options .
01691                         " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01692 
01693                 if ( $multi ) {
01694                         $first = true;
01695                         foreach ( $a as $row ) {
01696                                 if ( $first ) {
01697                                         $first = false;
01698                                 } else {
01699                                         $sql .= ',';
01700                                 }
01701                                 $sql .= '(' . $this->makeList( $row ) . ')';
01702                         }
01703                 } else {
01704                         $sql .= '(' . $this->makeList( $a ) . ')';
01705                 }
01706 
01707                 return (bool)$this->query( $sql, $fname );
01708         }
01709 
01716         protected function makeUpdateOptions( $options ) {
01717                 if ( !is_array( $options ) ) {
01718                         $options = array( $options );
01719                 }
01720 
01721                 $opts = array();
01722 
01723                 if ( in_array( 'LOW_PRIORITY', $options ) ) {
01724                         $opts[] = $this->lowPriorityOption();
01725                 }
01726 
01727                 if ( in_array( 'IGNORE', $options ) ) {
01728                         $opts[] = 'IGNORE';
01729                 }
01730 
01731                 return implode( ' ', $opts );
01732         }
01733 
01757         function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
01758                 $table = $this->tableName( $table );
01759                 $opts = $this->makeUpdateOptions( $options );
01760                 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01761 
01762                 if ( $conds !== array() && $conds !== '*' ) {
01763                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01764                 }
01765 
01766                 return $this->query( $sql, $fname );
01767         }
01768 
01783         public function makeList( $a, $mode = LIST_COMMA ) {
01784                 if ( !is_array( $a ) ) {
01785                         throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
01786                 }
01787 
01788                 $first = true;
01789                 $list = '';
01790 
01791                 foreach ( $a as $field => $value ) {
01792                         if ( !$first ) {
01793                                 if ( $mode == LIST_AND ) {
01794                                         $list .= ' AND ';
01795                                 } elseif ( $mode == LIST_OR ) {
01796                                         $list .= ' OR ';
01797                                 } else {
01798                                         $list .= ',';
01799                                 }
01800                         } else {
01801                                 $first = false;
01802                         }
01803 
01804                         if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
01805                                 $list .= "($value)";
01806                         } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
01807                                 $list .= "$value";
01808                         } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
01809                                 if ( count( $value ) == 0 ) {
01810                                         throw new MWException( __METHOD__ . ": empty input for field $field" );
01811                                 } elseif ( count( $value ) == 1 ) {
01812                                         // Special-case single values, as IN isn't terribly efficient
01813                                         // Don't necessarily assume the single key is 0; we don't
01814                                         // enforce linear numeric ordering on other arrays here.
01815                                         $value = array_values( $value );
01816                                         $list .= $field . " = " . $this->addQuotes( $value[0] );
01817                                 } else {
01818                                         $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
01819                                 }
01820                         } elseif ( $value === null ) {
01821                                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
01822                                         $list .= "$field IS ";
01823                                 } elseif ( $mode == LIST_SET ) {
01824                                         $list .= "$field = ";
01825                                 }
01826                                 $list .= 'NULL';
01827                         } else {
01828                                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
01829                                         $list .= "$field = ";
01830                                 }
01831                                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
01832                         }
01833                 }
01834 
01835                 return $list;
01836         }
01837 
01848         public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
01849                 $conds = array();
01850 
01851                 foreach ( $data as $base => $sub ) {
01852                         if ( count( $sub ) ) {
01853                                 $conds[] = $this->makeList(
01854                                         array( $baseKey => $base, $subKey => array_keys( $sub ) ),
01855                                         LIST_AND );
01856                         }
01857                 }
01858 
01859                 if ( $conds ) {
01860                         return $this->makeList( $conds, LIST_OR );
01861                 } else {
01862                         // Nothing to search for...
01863                         return false;
01864                 }
01865         }
01866 
01875         public function aggregateValue( $valuedata, $valuename = 'value' ) {
01876                 return $valuename;
01877         }
01878 
01883         public function bitNot( $field ) {
01884                 return "(~$field)";
01885         }
01886 
01892         public function bitAnd( $fieldLeft, $fieldRight ) {
01893                 return "($fieldLeft & $fieldRight)";
01894         }
01895 
01901         public function bitOr( $fieldLeft, $fieldRight ) {
01902                 return "($fieldLeft | $fieldRight)";
01903         }
01904 
01910         public function buildConcat( $stringList ) {
01911                 return 'CONCAT(' . implode( ',', $stringList ) . ')';
01912         }
01913 
01923         public function selectDB( $db ) {
01924                 # Stub.  Shouldn't cause serious problems if it's not overridden, but
01925                 # if your database engine supports a concept similar to MySQL's
01926                 # databases you may as well.
01927                 $this->mDBname = $db;
01928                 return true;
01929         }
01930 
01934         public function getDBname() {
01935                 return $this->mDBname;
01936         }
01937 
01941         public function getServer() {
01942                 return $this->mServer;
01943         }
01944 
01962         public function tableName( $name, $format = 'quoted' ) {
01963                 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
01964                 # Skip the entire process when we have a string quoted on both ends.
01965                 # Note that we check the end so that we will still quote any use of
01966                 # use of `database`.table. But won't break things if someone wants
01967                 # to query a database table with a dot in the name.
01968                 if ( $this->isQuotedIdentifier( $name ) ) {
01969                         return $name;
01970                 }
01971 
01972                 # Lets test for any bits of text that should never show up in a table
01973                 # name. Basically anything like JOIN or ON which are actually part of
01974                 # SQL queries, but may end up inside of the table value to combine
01975                 # sql. Such as how the API is doing.
01976                 # Note that we use a whitespace test rather than a \b test to avoid
01977                 # any remote case where a word like on may be inside of a table name
01978                 # surrounded by symbols which may be considered word breaks.
01979                 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
01980                         return $name;
01981                 }
01982 
01983                 # Split database and table into proper variables.
01984                 # We reverse the explode so that database.table and table both output
01985                 # the correct table.
01986                 $dbDetails = array_reverse( explode( '.', $name, 2 ) );
01987                 if ( isset( $dbDetails[1] ) ) {
01988                         list( $table, $database ) = $dbDetails;
01989                 } else {
01990                         list( $table ) = $dbDetails;
01991                 }
01992                 $prefix = $this->mTablePrefix; # Default prefix
01993 
01994                 # A database name has been specified in input. We don't want any
01995                 # prefixes added.
01996                 if ( isset( $database ) ) {
01997                         $prefix = '';
01998                 }
01999 
02000                 # Note that we use the long format because php will complain in in_array if
02001                 # the input is not an array, and will complain in is_array if it is not set.
02002                 if ( !isset( $database ) # Don't use shared database if pre selected.
02003                  && isset( $wgSharedDB ) # We have a shared database
02004                  && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
02005                  && isset( $wgSharedTables )
02006                  && is_array( $wgSharedTables )
02007                  && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
02008                         $database = $wgSharedDB;
02009                         $prefix   = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
02010                 }
02011 
02012                 # Quote the $database and $table and apply the prefix if not quoted.
02013                 if ( isset( $database ) ) {
02014                         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
02015                                 $database = $this->addIdentifierQuotes( $database );
02016                         }
02017                 }
02018 
02019                 $table = "{$prefix}{$table}";
02020                 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) {
02021                         $table = $this->addIdentifierQuotes( "{$table}" );
02022                 }
02023 
02024                 # Merge our database and table into our final table name.
02025                 $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
02026 
02027                 return $tableName;
02028         }
02029 
02041         public function tableNames() {
02042                 $inArray = func_get_args();
02043                 $retVal = array();
02044 
02045                 foreach ( $inArray as $name ) {
02046                         $retVal[$name] = $this->tableName( $name );
02047                 }
02048 
02049                 return $retVal;
02050         }
02051 
02063         public function tableNamesN() {
02064                 $inArray = func_get_args();
02065                 $retVal = array();
02066 
02067                 foreach ( $inArray as $name ) {
02068                         $retVal[] = $this->tableName( $name );
02069                 }
02070 
02071                 return $retVal;
02072         }
02073 
02082         public function tableNameWithAlias( $name, $alias = false ) {
02083                 if ( !$alias || $alias == $name ) {
02084                         return $this->tableName( $name );
02085                 } else {
02086                         return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02087                 }
02088         }
02089 
02096         public function tableNamesWithAlias( $tables ) {
02097                 $retval = array();
02098                 foreach ( $tables as $alias => $table ) {
02099                         if ( is_numeric( $alias ) ) {
02100                                 $alias = $table;
02101                         }
02102                         $retval[] = $this->tableNameWithAlias( $table, $alias );
02103                 }
02104                 return $retval;
02105         }
02106 
02115         public function fieldNameWithAlias( $name, $alias = false ) {
02116                 if ( !$alias || (string)$alias === (string)$name ) {
02117                         return $name;
02118                 } else {
02119                         return $name . ' AS ' . $alias; //PostgreSQL needs AS
02120                 }
02121         }
02122 
02129         public function fieldNamesWithAlias( $fields ) {
02130                 $retval = array();
02131                 foreach ( $fields as $alias => $field ) {
02132                         if ( is_numeric( $alias ) ) {
02133                                 $alias = $field;
02134                         }
02135                         $retval[] = $this->fieldNameWithAlias( $field, $alias );
02136                 }
02137                 return $retval;
02138         }
02139 
02149         protected function tableNamesWithUseIndexOrJOIN(
02150                 $tables, $use_index = array(), $join_conds = array()
02151         ) {
02152                 $ret = array();
02153                 $retJOIN = array();
02154                 $use_index = (array)$use_index;
02155                 $join_conds = (array)$join_conds;
02156 
02157                 foreach ( $tables as $alias => $table ) {
02158                         if ( !is_string( $alias ) ) {
02159                                 // No alias? Set it equal to the table name
02160                                 $alias = $table;
02161                         }
02162                         // Is there a JOIN clause for this table?
02163                         if ( isset( $join_conds[$alias] ) ) {
02164                                 list( $joinType, $conds ) = $join_conds[$alias];
02165                                 $tableClause = $joinType;
02166                                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02167                                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02168                                         $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02169                                         if ( $use != '' ) {
02170                                                 $tableClause .= ' ' . $use;
02171                                         }
02172                                 }
02173                                 $on = $this->makeList( (array)$conds, LIST_AND );
02174                                 if ( $on != '' ) {
02175                                         $tableClause .= ' ON (' . $on . ')';
02176                                 }
02177 
02178                                 $retJOIN[] = $tableClause;
02179                         // Is there an INDEX clause for this table?
02180                         } elseif ( isset( $use_index[$alias] ) ) {
02181                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02182                                 $tableClause .= ' ' . $this->useIndexClause(
02183                                         implode( ',', (array)$use_index[$alias] ) );
02184 
02185                                 $ret[] = $tableClause;
02186                         } else {
02187                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02188 
02189                                 $ret[] = $tableClause;
02190                         }
02191                 }
02192 
02193                 // We can't separate explicit JOIN clauses with ',', use ' ' for those
02194                 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02195                 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02196 
02197                 // Compile our final table clause
02198                 return implode( ' ', array( $straightJoins, $otherJoins ) );
02199         }
02200 
02208         protected function indexName( $index ) {
02209                 // Backwards-compatibility hack
02210                 $renamed = array(
02211                         'ar_usertext_timestamp' => 'usertext_timestamp',
02212                         'un_user_id'            => 'user_id',
02213                         'un_user_ip'            => 'user_ip',
02214                 );
02215 
02216                 if ( isset( $renamed[$index] ) ) {
02217                         return $renamed[$index];
02218                 } else {
02219                         return $index;
02220                 }
02221         }
02222 
02231         public function addQuotes( $s ) {
02232                 if ( $s === null ) {
02233                         return 'NULL';
02234                 } else {
02235                         # This will also quote numeric values. This should be harmless,
02236                         # and protects against weird problems that occur when they really
02237                         # _are_ strings such as article titles and string->number->string
02238                         # conversion is not 1:1.
02239                         return "'" . $this->strencode( $s ) . "'";
02240                 }
02241         }
02242 
02253         public function addIdentifierQuotes( $s ) {
02254                 return '"' . str_replace( '"', '""', $s ) . '"';
02255         }
02256 
02265         public function isQuotedIdentifier( $name ) {
02266                 return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02267         }
02268 
02273         protected function escapeLikeInternal( $s ) {
02274                 $s = str_replace( '\\', '\\\\', $s );
02275                 $s = $this->strencode( $s );
02276                 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
02277 
02278                 return $s;
02279         }
02280 
02293         public function buildLike() {
02294                 $params = func_get_args();
02295 
02296                 if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02297                         $params = $params[0];
02298                 }
02299 
02300                 $s = '';
02301 
02302                 foreach ( $params as $value ) {
02303                         if ( $value instanceof LikeMatch ) {
02304                                 $s .= $value->toString();
02305                         } else {
02306                                 $s .= $this->escapeLikeInternal( $value );
02307                         }
02308                 }
02309 
02310                 return " LIKE '" . $s . "' ";
02311         }
02312 
02318         public function anyChar() {
02319                 return new LikeMatch( '_' );
02320         }
02321 
02327         public function anyString() {
02328                 return new LikeMatch( '%' );
02329         }
02330 
02342         public function nextSequenceValue( $seqName ) {
02343                 return null;
02344         }
02345 
02356         public function useIndexClause( $index ) {
02357                 return '';
02358         }
02359 
02382         public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
02383                 $quotedTable = $this->tableName( $table );
02384 
02385                 if ( count( $rows ) == 0 ) {
02386                         return;
02387                 }
02388 
02389                 # Single row case
02390                 if ( !is_array( reset( $rows ) ) ) {
02391                         $rows = array( $rows );
02392                 }
02393 
02394                 foreach( $rows as $row ) {
02395                         # Delete rows which collide
02396                         if ( $uniqueIndexes ) {
02397                                 $sql = "DELETE FROM $quotedTable WHERE ";
02398                                 $first = true;
02399                                 foreach ( $uniqueIndexes as $index ) {
02400                                         if ( $first ) {
02401                                                 $first = false;
02402                                                 $sql .= '( ';
02403                                         } else {
02404                                                 $sql .= ' ) OR ( ';
02405                                         }
02406                                         if ( is_array( $index ) ) {
02407                                                 $first2 = true;
02408                                                 foreach ( $index as $col ) {
02409                                                         if ( $first2 ) {
02410                                                                 $first2 = false;
02411                                                         } else {
02412                                                                 $sql .= ' AND ';
02413                                                         }
02414                                                         $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02415                                                 }
02416                                         } else {
02417                                                 $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02418                                         }
02419                                 }
02420                                 $sql .= ' )';
02421                                 $this->query( $sql, $fname );
02422                         }
02423 
02424                         # Now insert the row
02425                         $this->insert( $table, $row );
02426                 }
02427         }
02428 
02439         protected function nativeReplace( $table, $rows, $fname ) {
02440                 $table = $this->tableName( $table );
02441 
02442                 # Single row case
02443                 if ( !is_array( reset( $rows ) ) ) {
02444                         $rows = array( $rows );
02445                 }
02446 
02447                 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02448                 $first = true;
02449 
02450                 foreach ( $rows as $row ) {
02451                         if ( $first ) {
02452                                 $first = false;
02453                         } else {
02454                                 $sql .= ',';
02455                         }
02456 
02457                         $sql .= '(' . $this->makeList( $row ) . ')';
02458                 }
02459 
02460                 return $this->query( $sql, $fname );
02461         }
02462 
02484         public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02485                 $fname = 'DatabaseBase::deleteJoin' )
02486         {
02487                 if ( !$conds ) {
02488                         throw new DBUnexpectedError( $this,
02489                                 'DatabaseBase::deleteJoin() called with empty $conds' );
02490                 }
02491 
02492                 $delTable = $this->tableName( $delTable );
02493                 $joinTable = $this->tableName( $joinTable );
02494                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02495                 if ( $conds != '*' ) {
02496                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02497                 }
02498                 $sql .= ')';
02499 
02500                 $this->query( $sql, $fname );
02501         }
02502 
02511         public function textFieldSize( $table, $field ) {
02512                 $table = $this->tableName( $table );
02513                 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02514                 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02515                 $row = $this->fetchObject( $res );
02516 
02517                 $m = array();
02518 
02519                 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02520                         $size = $m[1];
02521                 } else {
02522                         $size = -1;
02523                 }
02524 
02525                 return $size;
02526         }
02527 
02536         public function lowPriorityOption() {
02537                 return '';
02538         }
02539 
02551         public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
02552                 if ( !$conds ) {
02553                         throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02554                 }
02555 
02556                 $table = $this->tableName( $table );
02557                 $sql = "DELETE FROM $table";
02558 
02559                 if ( $conds != '*' ) {
02560                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
02561                 }
02562 
02563                 return $this->query( $sql, $fname );
02564         }
02565 
02592         public function insertSelect( $destTable, $srcTable, $varMap, $conds,
02593                 $fname = 'DatabaseBase::insertSelect',
02594                 $insertOptions = array(), $selectOptions = array() )
02595         {
02596                 $destTable = $this->tableName( $destTable );
02597 
02598                 if ( is_array( $insertOptions ) ) {
02599                         $insertOptions = implode( ' ', $insertOptions );
02600                 }
02601 
02602                 if ( !is_array( $selectOptions ) ) {
02603                         $selectOptions = array( $selectOptions );
02604                 }
02605 
02606                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
02607 
02608                 if ( is_array( $srcTable ) ) {
02609                         $srcTable =  implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
02610                 } else {
02611                         $srcTable = $this->tableName( $srcTable );
02612                 }
02613 
02614                 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
02615                         " SELECT $startOpts " . implode( ',', $varMap ) .
02616                         " FROM $srcTable $useIndex ";
02617 
02618                 if ( $conds != '*' ) {
02619                         if ( is_array( $conds ) ) {
02620                                 $conds = $this->makeList( $conds, LIST_AND );
02621                         }
02622                         $sql .= " WHERE $conds";
02623                 }
02624 
02625                 $sql .= " $tailOpts";
02626 
02627                 return $this->query( $sql, $fname );
02628         }
02629 
02650         public function limitResult( $sql, $limit, $offset = false ) {
02651                 if ( !is_numeric( $limit ) ) {
02652                         throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
02653                 }
02654                 return "$sql LIMIT "
02655                         . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
02656                         . "{$limit} ";
02657         }
02658 
02664         public function unionSupportsOrderAndLimit() {
02665                 return true; // True for almost every DB supported
02666         }
02667 
02676         public function unionQueries( $sqls, $all ) {
02677                 $glue = $all ? ') UNION ALL (' : ') UNION (';
02678                 return '(' . implode( $glue, $sqls ) . ')';
02679         }
02680 
02690         public function conditional( $cond, $trueVal, $falseVal ) {
02691                 if ( is_array( $cond ) ) {
02692                         $cond = $this->makeList( $cond, LIST_AND );
02693                 }
02694                 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
02695         }
02696 
02707         public function strreplace( $orig, $old, $new ) {
02708                 return "REPLACE({$orig}, {$old}, {$new})";
02709         }
02710 
02717         public function getServerUptime() {
02718                 return 0;
02719         }
02720 
02727         public function wasDeadlock() {
02728                 return false;
02729         }
02730 
02737         public function wasLockTimeout() {
02738                 return false;
02739         }
02740 
02748         public function wasErrorReissuable() {
02749                 return false;
02750         }
02751 
02758         public function wasReadOnlyError() {
02759                 return false;
02760         }
02761 
02780         public function deadlockLoop() {
02781                 $this->begin( __METHOD__ );
02782                 $args = func_get_args();
02783                 $function = array_shift( $args );
02784                 $oldIgnore = $this->ignoreErrors( true );
02785                 $tries = DEADLOCK_TRIES;
02786 
02787                 if ( is_array( $function ) ) {
02788                         $fname = $function[0];
02789                 } else {
02790                         $fname = $function;
02791                 }
02792 
02793                 do {
02794                         $retVal = call_user_func_array( $function, $args );
02795                         $error = $this->lastError();
02796                         $errno = $this->lastErrno();
02797                         $sql = $this->lastQuery();
02798 
02799                         if ( $errno ) {
02800                                 if ( $this->wasDeadlock() ) {
02801                                         # Retry
02802                                         usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
02803                                 } else {
02804                                         $this->reportQueryError( $error, $errno, $sql, $fname );
02805                                 }
02806                         }
02807                 } while ( $this->wasDeadlock() && --$tries > 0 );
02808 
02809                 $this->ignoreErrors( $oldIgnore );
02810 
02811                 if ( $tries <= 0 ) {
02812                         $this->rollback( __METHOD__ );
02813                         $this->reportQueryError( $error, $errno, $sql, $fname );
02814                         return false;
02815                 } else {
02816                         $this->commit( __METHOD__ );
02817                         return $retVal;
02818                 }
02819         }
02820 
02832         public function masterPosWait( DBMasterPos $pos, $timeout ) {
02833                 wfProfileIn( __METHOD__ );
02834 
02835                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02836                         $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
02837 
02838                         if ( $wait > $timeout * 1e6 ) {
02839                                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
02840                                 wfProfileOut( __METHOD__ );
02841                                 return -1;
02842                         } elseif ( $wait > 0 ) {
02843                                 wfDebug( "Fake slave waiting $wait us\n" );
02844                                 usleep( $wait );
02845                                 wfProfileOut( __METHOD__ );
02846                                 return 1;
02847                         } else {
02848                                 wfDebug( "Fake slave up to date ($wait us)\n" );
02849                                 wfProfileOut( __METHOD__ );
02850                                 return 0;
02851                         }
02852                 }
02853 
02854                 wfProfileOut( __METHOD__ );
02855 
02856                 # Real waits are implemented in the subclass.
02857                 return 0;
02858         }
02859 
02865         public function getSlavePos() {
02866                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02867                         $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
02868                         wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
02869                         return $pos;
02870                 } else {
02871                         # Stub
02872                         return false;
02873                 }
02874         }
02875 
02881         public function getMasterPos() {
02882                 if ( $this->mFakeMaster ) {
02883                         return new MySQLMasterPos( 'fake', microtime( true ) );
02884                 } else {
02885                         return false;
02886                 }
02887         }
02888 
02900         final public function onTransactionIdle( Closure $callback ) {
02901                 if ( $this->mTrxLevel ) {
02902                         $this->trxIdleCallbacks[] = $callback;
02903                 } else {
02904                         $callback();
02905                 }
02906         }
02907 
02913         protected function runOnTransactionIdleCallbacks() {
02914                 $e = null; // last exception
02915                 do { // callbacks may add callbacks :)
02916                         $callbacks = $this->trxIdleCallbacks;
02917                         $this->trxIdleCallbacks = array(); // recursion guard
02918                         foreach ( $callbacks as $callback ) {
02919                                 try {
02920                                         $callback();
02921                                 } catch ( Exception $e ) {}
02922                         }
02923                 } while ( count( $this->trxIdleCallbacks ) );
02924 
02925                 if ( $e instanceof Exception ) {
02926                         throw $e; // re-throw any last exception
02927                 }
02928         }
02929 
02942         final public function begin( $fname = 'DatabaseBase::begin' ) {
02943                 global $wgDebugDBTransactions;
02944 
02945                 if ( $this->mTrxLevel ) { // implicit commit
02946                         if ( !$this->mTrxAutomatic ) {
02947                                 // We want to warn about inadvertently nested begin/commit pairs, but not about auto-committing
02948                                 // implicit transactions that were started by query() because DBO_TRX was set.
02949 
02950                                 wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
02951                                         " performing implicit commit!" );
02952                         } else {
02953                                 // if the transaction was automatic and has done write operations,
02954                                 // log it if $wgDebugDBTransactions is enabled.
02955 
02956                                 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
02957                                         wfDebug( "$fname: Automatic transaction with writes in progress (from {$this->mTrxFname}), " .
02958                                                 " performing implicit commit!\n" );
02959                                 }
02960                         }
02961 
02962                         $this->doCommit( $fname );
02963                         $this->runOnTransactionIdleCallbacks();
02964                 }
02965 
02966                 $this->doBegin( $fname );
02967                 $this->mTrxFname = $fname;
02968                 $this->mTrxDoneWrites = false;
02969                 $this->mTrxAutomatic = false;
02970         }
02971 
02978         protected function doBegin( $fname ) {
02979                 $this->query( 'BEGIN', $fname );
02980                 $this->mTrxLevel = 1;
02981         }
02982 
02995         final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) {
02996                 if ( $flush != 'flush' ) {
02997                         if ( !$this->mTrxLevel ) {
02998                                 wfWarn( "$fname: No transaction to commit, something got out of sync!" );
02999                         } elseif( $this->mTrxAutomatic ) {
03000                                 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
03001                         }
03002                 } else {
03003                         if ( !$this->mTrxLevel ) {
03004                                 return; // nothing to do
03005                         } elseif( !$this->mTrxAutomatic ) {
03006                                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03007                         }
03008                 }
03009 
03010                 $this->doCommit( $fname );
03011                 $this->runOnTransactionIdleCallbacks();
03012         }
03013 
03020         protected function doCommit( $fname ) {
03021                 if ( $this->mTrxLevel ) {
03022                         $this->query( 'COMMIT', $fname );
03023                         $this->mTrxLevel = 0;
03024                 }
03025         }
03026 
03035         final public function rollback( $fname = 'DatabaseBase::rollback' ) {
03036                 if ( !$this->mTrxLevel ) {
03037                         wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
03038                 }
03039                 $this->doRollback( $fname );
03040                 $this->trxIdleCallbacks = array(); // cancel
03041         }
03042 
03049         protected function doRollback( $fname ) {
03050                 if ( $this->mTrxLevel ) {
03051                         $this->query( 'ROLLBACK', $fname, true );
03052                         $this->mTrxLevel = 0;
03053                 }
03054         }
03055 
03071         public function duplicateTableStructure( $oldName, $newName, $temporary = false,
03072                 $fname = 'DatabaseBase::duplicateTableStructure' )
03073         {
03074                 throw new MWException(
03075                         'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
03076         }
03077 
03085         function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
03086                 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
03087         }
03088 
03100         public function timestamp( $ts = 0 ) {
03101                 return wfTimestamp( TS_MW, $ts );
03102         }
03103 
03117         public function timestampOrNull( $ts = null ) {
03118                 if ( is_null( $ts ) ) {
03119                         return null;
03120                 } else {
03121                         return $this->timestamp( $ts );
03122                 }
03123         }
03124 
03140         public function resultObject( $result ) {
03141                 if ( empty( $result ) ) {
03142                         return false;
03143                 } elseif ( $result instanceof ResultWrapper ) {
03144                         return $result;
03145                 } elseif ( $result === true ) {
03146                         // Successful write query
03147                         return $result;
03148                 } else {
03149                         return new ResultWrapper( $this, $result );
03150                 }
03151         }
03152 
03158         public function ping() {
03159                 # Stub.  Not essential to override.
03160                 return true;
03161         }
03162 
03172         public function getLag() {
03173                 return intval( $this->mFakeSlaveLag );
03174         }
03175 
03181         function maxListLen() {
03182                 return 0;
03183         }
03184 
03193         public function encodeBlob( $b ) {
03194                 return $b;
03195         }
03196 
03204         public function decodeBlob( $b ) {
03205                 return $b;
03206         }
03207 
03218         public function setSessionOptions( array $options ) {}
03219 
03234         public function sourceFile(
03235                 $filename, $lineCallback = false, $resultCallback = false, $fname = false
03236         ) {
03237                 wfSuppressWarnings();
03238                 $fp = fopen( $filename, 'r' );
03239                 wfRestoreWarnings();
03240 
03241                 if ( false === $fp ) {
03242                         throw new MWException( "Could not open \"{$filename}\".\n" );
03243                 }
03244 
03245                 if ( !$fname ) {
03246                         $fname = __METHOD__ . "( $filename )";
03247                 }
03248 
03249                 try {
03250                         $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
03251                 }
03252                 catch ( MWException $e ) {
03253                         fclose( $fp );
03254                         throw $e;
03255                 }
03256 
03257                 fclose( $fp );
03258 
03259                 return $error;
03260         }
03261 
03270         public function patchPath( $patch ) {
03271                 global $IP;
03272 
03273                 $dbType = $this->getType();
03274                 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03275                         return "$IP/maintenance/$dbType/archives/$patch";
03276                 } else {
03277                         return "$IP/maintenance/archives/$patch";
03278                 }
03279         }
03280 
03288         public function setSchemaVars( $vars ) {
03289                 $this->mSchemaVars = $vars;
03290         }
03291 
03305         public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03306                 $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
03307         {
03308                 $cmd = '';
03309 
03310                 while ( !feof( $fp ) ) {
03311                         if ( $lineCallback ) {
03312                                 call_user_func( $lineCallback );
03313                         }
03314 
03315                         $line = trim( fgets( $fp ) );
03316 
03317                         if ( $line == '' ) {
03318                                 continue;
03319                         }
03320 
03321                         if ( '-' == $line[0] && '-' == $line[1] ) {
03322                                 continue;
03323                         }
03324 
03325                         if ( $cmd != '' ) {
03326                                 $cmd .= ' ';
03327                         }
03328 
03329                         $done = $this->streamStatementEnd( $cmd, $line );
03330 
03331                         $cmd .= "$line\n";
03332 
03333                         if ( $done || feof( $fp ) ) {
03334                                 $cmd = $this->replaceVars( $cmd );
03335                                 if ( $inputCallback ) {
03336                                         call_user_func( $inputCallback, $cmd );
03337                                 }
03338                                 $res = $this->query( $cmd, $fname );
03339 
03340                                 if ( $resultCallback ) {
03341                                         call_user_func( $resultCallback, $res, $this );
03342                                 }
03343 
03344                                 if ( false === $res ) {
03345                                         $err = $this->lastError();
03346                                         return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03347                                 }
03348 
03349                                 $cmd = '';
03350                         }
03351                 }
03352 
03353                 return true;
03354         }
03355 
03363         public function streamStatementEnd( &$sql, &$newLine ) {
03364                 if ( $this->delimiter ) {
03365                         $prev = $newLine;
03366                         $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
03367                         if ( $newLine != $prev ) {
03368                                 return true;
03369                         }
03370                 }
03371                 return false;
03372         }
03373 
03391         protected function replaceSchemaVars( $ins ) {
03392                 $vars = $this->getSchemaVars();
03393                 foreach ( $vars as $var => $value ) {
03394                         // replace '{$var}'
03395                         $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
03396                         // replace `{$var}`
03397                         $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
03398                         // replace /*$var*/
03399                         $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins );
03400                 }
03401                 return $ins;
03402         }
03403 
03411         protected function replaceVars( $ins ) {
03412                 $ins = $this->replaceSchemaVars( $ins );
03413 
03414                 // Table prefixes
03415                 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
03416                         array( $this, 'tableNameCallback' ), $ins );
03417 
03418                 // Index names
03419                 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
03420                         array( $this, 'indexNameCallback' ), $ins );
03421 
03422                 return $ins;
03423         }
03424 
03431         protected function getSchemaVars() {
03432                 if ( $this->mSchemaVars ) {
03433                         return $this->mSchemaVars;
03434                 } else {
03435                         return $this->getDefaultSchemaVars();
03436                 }
03437         }
03438 
03447         protected function getDefaultSchemaVars() {
03448                 return array();
03449         }
03450 
03458         protected function tableNameCallback( $matches ) {
03459                 return $this->tableName( $matches[1] );
03460         }
03461 
03469         protected function indexNameCallback( $matches ) {
03470                 return $this->indexName( $matches[1] );
03471         }
03472 
03481         public function lockIsFree( $lockName, $method ) {
03482                 return true;
03483         }
03484 
03496         public function lock( $lockName, $method, $timeout = 5 ) {
03497                 return true;
03498         }
03499 
03510         public function unlock( $lockName, $method ) {
03511                 return true;
03512         }
03513 
03524         public function lockTables( $read, $write, $method, $lowPriority = true ) {
03525                 return true;
03526         }
03527 
03535         public function unlockTables( $method ) {
03536                 return true;
03537         }
03538 
03546         public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
03547                 if( !$this->tableExists( $tableName, $fName ) ) {
03548                         return false;
03549                 }
03550                 $sql = "DROP TABLE " . $this->tableName( $tableName );
03551                 if( $this->cascadingDeletes() ) {
03552                         $sql .= " CASCADE";
03553                 }
03554                 return $this->query( $sql, $fName );
03555         }
03556 
03563         public function getSearchEngine() {
03564                 return 'SearchEngineDummy';
03565         }
03566 
03574         public function getInfinity() {
03575                 return 'infinity';
03576         }
03577 
03584         public function encodeExpiry( $expiry ) {
03585                 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
03586                         ? $this->getInfinity()
03587                         : $this->timestamp( $expiry );
03588         }
03589 
03597         public function decodeExpiry( $expiry, $format = TS_MW ) {
03598                 return ( $expiry == '' || $expiry == $this->getInfinity() )
03599                         ? 'infinity'
03600                         : wfTimestamp( $format, $expiry );
03601         }
03602 
03612         public function setBigSelects( $value = true ) {
03613                 // no-op
03614         }
03615 
03619         public function __toString() {
03620                 return (string)$this->mConn;
03621         }
03622 }