MediaWiki  REL1_19
Database.php
Go to the documentation of this file.
00001 <?php
00012 define( 'DEADLOCK_TRIES', 4 );
00014 define( 'DEADLOCK_DELAY_MIN', 500000 );
00016 define( 'DEADLOCK_DELAY_MAX', 1500000 );
00017 
00025 interface DatabaseType {
00031         function getType();
00032 
00043         function open( $server, $user, $password, $dbName );
00044 
00054         function fetchObject( $res );
00055 
00064         function fetchRow( $res );
00065 
00072         function numRows( $res );
00073 
00081         function numFields( $res );
00082 
00091         function fieldName( $res, $n );
00092 
00105         function insertId();
00106 
00114         function dataSeek( $res, $row );
00115 
00122         function lastErrno();
00123 
00130         function lastError();
00131 
00141         function fieldInfo( $table, $field );
00142 
00150         function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
00151 
00158         function affectedRows();
00159 
00166         function strencode( $s );
00167 
00176         static function getSoftwareLink();
00177 
00184         function getServerVersion();
00185 
00193         function getServerInfo();
00194 }
00195 
00200 abstract class DatabaseBase implements DatabaseType {
00201 
00202 # ------------------------------------------------------------------------------
00203 # Variables
00204 # ------------------------------------------------------------------------------
00205 
00206         protected $mLastQuery = '';
00207         protected $mDoneWrites = false;
00208         protected $mPHPError = false;
00209 
00210         protected $mServer, $mUser, $mPassword, $mDBname;
00211 
00215         protected $mConn = null;
00216         protected $mOpened = false;
00217 
00218         protected $mTablePrefix;
00219         protected $mFlags;
00220         protected $mTrxLevel = 0;
00221         protected $mErrorCount = 0;
00222         protected $mLBInfo = array();
00223         protected $mFakeSlaveLag = null, $mFakeMaster = false;
00224         protected $mDefaultBigSelects = null;
00225         protected $mSchemaVars = false;
00226 
00227         protected $preparedArgs;
00228 
00229         protected $htmlErrors;
00230 
00231         protected $delimiter = ';';
00232 
00233 # ------------------------------------------------------------------------------
00234 # Accessors
00235 # ------------------------------------------------------------------------------
00236         # These optionally set a variable and return the previous state
00237 
00245         public function getServerInfo() {
00246                 return $this->getServerVersion();
00247         }
00248 
00258         function debug( $debug = null ) {
00259                 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00260         }
00261 
00284         function bufferResults( $buffer = null ) {
00285                 if ( is_null( $buffer ) ) {
00286                         return !(bool)( $this->mFlags & DBO_NOBUFFER );
00287                 } else {
00288                         return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00289                 }
00290         }
00291 
00303         function ignoreErrors( $ignoreErrors = null ) {
00304                 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00305         }
00306 
00316         function trxLevel( $level = null ) {
00317                 return wfSetVar( $this->mTrxLevel, $level );
00318         }
00319 
00325         function errorCount( $count = null ) {
00326                 return wfSetVar( $this->mErrorCount, $count );
00327         }
00328 
00334         function tablePrefix( $prefix = null ) {
00335                 return wfSetVar( $this->mTablePrefix, $prefix );
00336         }
00337 
00347         function getLBInfo( $name = null ) {
00348                 if ( is_null( $name ) ) {
00349                         return $this->mLBInfo;
00350                 } else {
00351                         if ( array_key_exists( $name, $this->mLBInfo ) ) {
00352                                 return $this->mLBInfo[$name];
00353                         } else {
00354                                 return null;
00355                         }
00356                 }
00357         }
00358 
00367         function setLBInfo( $name, $value = null ) {
00368                 if ( is_null( $value ) ) {
00369                         $this->mLBInfo = $name;
00370                 } else {
00371                         $this->mLBInfo[$name] = $value;
00372                 }
00373         }
00374 
00380         function setFakeSlaveLag( $lag ) {
00381                 $this->mFakeSlaveLag = $lag;
00382         }
00383 
00389         function setFakeMaster( $enabled = true ) {
00390                 $this->mFakeMaster = $enabled;
00391         }
00392 
00398         function cascadingDeletes() {
00399                 return false;
00400         }
00401 
00407         function cleanupTriggers() {
00408                 return false;
00409         }
00410 
00417         function strictIPs() {
00418                 return false;
00419         }
00420 
00426         function realTimestamps() {
00427                 return false;
00428         }
00429 
00435         function implicitGroupby() {
00436                 return true;
00437         }
00438 
00445         function implicitOrderby() {
00446                 return true;
00447         }
00448 
00455         function standardSelectDistinct() {
00456                 return true;
00457         }
00458 
00465         function searchableIPs() {
00466                 return false;
00467         }
00468 
00474         function functionalIndexes() {
00475                 return false;
00476         }
00477 
00482         function lastQuery() {
00483                 return $this->mLastQuery;
00484         }
00485 
00492         function doneWrites() {
00493                 return $this->mDoneWrites;
00494         }
00495 
00500         function isOpen() {
00501                 return $this->mOpened;
00502         }
00503 
00516         function setFlag( $flag ) {
00517                 $this->mFlags |= $flag;
00518         }
00519 
00525         function clearFlag( $flag ) {
00526                 $this->mFlags &= ~$flag;
00527         }
00528 
00535         function getFlag( $flag ) {
00536                 return !!( $this->mFlags & $flag );
00537         }
00538 
00546         function getProperty( $name ) {
00547                 return $this->$name;
00548         }
00549 
00553         function getWikiID() {
00554                 if ( $this->mTablePrefix ) {
00555                         return "{$this->mDBname}-{$this->mTablePrefix}";
00556                 } else {
00557                         return $this->mDBname;
00558                 }
00559         }
00560 
00566         public function getSchemaPath() {
00567                 global $IP;
00568                 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
00569                         return "$IP/maintenance/" . $this->getType() . "/tables.sql";
00570                 } else {
00571                         return "$IP/maintenance/tables.sql";
00572                 }
00573         }
00574 
00575 # ------------------------------------------------------------------------------
00576 # Other functions
00577 # ------------------------------------------------------------------------------
00578 
00588         function __construct( $server = false, $user = false, $password = false, $dbName = false,
00589                 $flags = 0, $tablePrefix = 'get from global'
00590         ) {
00591                 global $wgDBprefix, $wgCommandLineMode;
00592 
00593                 $this->mFlags = $flags;
00594 
00595                 if ( $this->mFlags & DBO_DEFAULT ) {
00596                         if ( $wgCommandLineMode ) {
00597                                 $this->mFlags &= ~DBO_TRX;
00598                         } else {
00599                                 $this->mFlags |= DBO_TRX;
00600                         }
00601                 }
00602 
00604                 if ( $tablePrefix == 'get from global' ) {
00605                         $this->mTablePrefix = $wgDBprefix;
00606                 } else {
00607                         $this->mTablePrefix = $tablePrefix;
00608                 }
00609 
00610                 if ( $user ) {
00611                         $this->open( $server, $user, $password, $dbName );
00612                 }
00613         }
00614 
00620         public function __sleep() {
00621                 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
00622         }
00623 
00635         static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
00636                 wfDeprecated( __METHOD__, '1.17' );
00637                 return new DatabaseMysql( $server, $user, $password, $dbName, $flags );
00638         }
00639 
00645         public final static function newFromType( $dbType, $p = array() ) {
00646                 wfDeprecated( __METHOD__, '1.18' );
00647                 if ( isset( $p['tableprefix'] ) ) {
00648                         $p['tablePrefix'] = $p['tableprefix'];
00649                 }
00650                 return self::factory( $dbType, $p );
00651         }
00652 
00673         public final static function factory( $dbType, $p = array() ) {
00674                 $canonicalDBTypes = array(
00675                         'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
00676                 );
00677                 $dbType = strtolower( $dbType );
00678                 $class = 'Database' . ucfirst( $dbType );
00679 
00680                 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
00681                         return new $class(
00682                                 isset( $p['host'] ) ? $p['host'] : false,
00683                                 isset( $p['user'] ) ? $p['user'] : false,
00684                                 isset( $p['password'] ) ? $p['password'] : false,
00685                                 isset( $p['dbname'] ) ? $p['dbname'] : false,
00686                                 isset( $p['flags'] ) ? $p['flags'] : 0,
00687                                 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
00688                         );
00689                 } else {
00690                         return null;
00691                 }
00692         }
00693 
00694         protected function installErrorHandler() {
00695                 $this->mPHPError = false;
00696                 $this->htmlErrors = ini_set( 'html_errors', '0' );
00697                 set_error_handler( array( $this, 'connectionErrorHandler' ) );
00698         }
00699 
00703         protected function restoreErrorHandler() {
00704                 restore_error_handler();
00705                 if ( $this->htmlErrors !== false ) {
00706                         ini_set( 'html_errors', $this->htmlErrors );
00707                 }
00708                 if ( $this->mPHPError ) {
00709                         $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00710                         $error = preg_replace( '!^.*?:(.*)$!', '$1', $error );
00711                         return $error;
00712                 } else {
00713                         return false;
00714                 }
00715         }
00716 
00721         protected function connectionErrorHandler( $errno,  $errstr ) {
00722                 $this->mPHPError = $errstr;
00723         }
00724 
00731         function close() {
00732                 # Stub, should probably be overridden
00733                 return true;
00734         }
00735 
00739         function reportConnectionError( $error = 'Unknown error' ) {
00740                 $myError = $this->lastError();
00741                 if ( $myError ) {
00742                         $error = $myError;
00743                 }
00744 
00745                 # New method
00746                 throw new DBConnectionError( $this, $error );
00747         }
00748 
00755         protected abstract function doQuery( $sql );
00756 
00765         function isWriteQuery( $sql ) {
00766                 return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
00767         }
00768 
00791         public function query( $sql, $fname = '', $tempIgnore = false ) {
00792                 $isMaster = !is_null( $this->getLBInfo( 'master' ) );
00793                 if ( !Profiler::instance()->isStub() ) {
00794                         # generalizeSQL will probably cut down the query to reasonable
00795                         # logging size most of the time. The substr is really just a sanity check.
00796 
00797                         if ( $isMaster ) {
00798                                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00799                                 $totalProf = 'DatabaseBase::query-master';
00800                         } else {
00801                                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00802                                 $totalProf = 'DatabaseBase::query';
00803                         }
00804 
00805                         wfProfileIn( $totalProf );
00806                         wfProfileIn( $queryProf );
00807                 }
00808 
00809                 $this->mLastQuery = $sql;
00810                 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
00811                         # Set a flag indicating that writes have been done
00812                         wfDebug( __METHOD__ . ": Writes done: $sql\n" );
00813                         $this->mDoneWrites = true;
00814                 }
00815 
00816                 # Add a comment for easy SHOW PROCESSLIST interpretation
00817                 global $wgUser;
00818                 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
00819                         $userName = $wgUser->getName();
00820                         if ( mb_strlen( $userName ) > 15 ) {
00821                                 $userName = mb_substr( $userName, 0, 15 ) . '...';
00822                         }
00823                         $userName = str_replace( '/', '', $userName );
00824                 } else {
00825                         $userName = '';
00826                 }
00827                 $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
00828 
00829                 # If DBO_TRX is set, start a transaction
00830                 if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
00831                         $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) {
00832                         # avoid establishing transactions for SHOW and SET statements too -
00833                         # that would delay transaction initializations to once connection
00834                         # is really used by application
00835                         $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
00836                         if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 )
00837                                 $this->begin( __METHOD__ . " ($fname)" );
00838                 }
00839 
00840                 if ( $this->debug() ) {
00841                         static $cnt = 0;
00842 
00843                         $cnt++;
00844                         $sqlx = substr( $commentedSql, 0, 500 );
00845                         $sqlx = strtr( $sqlx, "\t\n", '  ' );
00846 
00847                         $master = $isMaster ? 'master' : 'slave';
00848                         wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
00849                 }
00850 
00851                 if ( istainted( $sql ) & TC_MYSQL ) {
00852                         throw new MWException( 'Tainted query found' );
00853                 }
00854 
00855                 $queryId = MWDebug::query( $sql, $fname, $isMaster );
00856 
00857                 # Do the query and handle errors
00858                 $ret = $this->doQuery( $commentedSql );
00859 
00860                 MWDebug::queryTime( $queryId );
00861 
00862                 # Try reconnecting if the connection was lost
00863                 if ( false === $ret && $this->wasErrorReissuable() ) {
00864                         # Transaction is gone, like it or not
00865                         $this->mTrxLevel = 0;
00866                         wfDebug( "Connection lost, reconnecting...\n" );
00867 
00868                         if ( $this->ping() ) {
00869                                 wfDebug( "Reconnected\n" );
00870                                 $sqlx = substr( $commentedSql, 0, 500 );
00871                                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
00872                                 global $wgRequestTime;
00873                                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
00874                                 if ( $elapsed < 300 ) {
00875                                         # Not a database error to lose a transaction after a minute or two
00876                                         wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
00877                                 }
00878                                 $ret = $this->doQuery( $commentedSql );
00879                         } else {
00880                                 wfDebug( "Failed\n" );
00881                         }
00882                 }
00883 
00884                 if ( false === $ret ) {
00885                         $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
00886                 }
00887 
00888                 if ( !Profiler::instance()->isStub() ) {
00889                         wfProfileOut( $queryProf );
00890                         wfProfileOut( $totalProf );
00891                 }
00892 
00893                 return $this->resultObject( $ret );
00894         }
00895 
00906         function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
00907                 # Ignore errors during error handling to avoid infinite recursion
00908                 $ignore = $this->ignoreErrors( true );
00909                 ++$this->mErrorCount;
00910 
00911                 if ( $ignore || $tempIgnore ) {
00912                         wfDebug( "SQL ERROR (ignored): $error\n" );
00913                         $this->ignoreErrors( $ignore );
00914                 } else {
00915                         $sql1line = str_replace( "\n", "\\n", $sql );
00916                         wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
00917                         wfDebug( "SQL ERROR: " . $error . "\n" );
00918                         throw new DBQueryError( $this, $error, $errno, $sql, $fname );
00919                 }
00920         }
00921 
00940         function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
00941                 /* MySQL doesn't support prepared statements (yet), so just
00942                    pack up the query for reference. We'll manually replace
00943                    the bits later. */
00944                 return array( 'query' => $sql, 'func' => $func );
00945         }
00946 
00951         function freePrepared( $prepared ) {
00952                 /* No-op by default */
00953         }
00954 
00962         function execute( $prepared, $args = null ) {
00963                 if ( !is_array( $args ) ) {
00964                         # Pull the var args
00965                         $args = func_get_args();
00966                         array_shift( $args );
00967                 }
00968 
00969                 $sql = $this->fillPrepared( $prepared['query'], $args );
00970 
00971                 return $this->query( $sql, $prepared['func'] );
00972         }
00973 
00987         function safeQuery( $query, $args = null ) {
00988                 $prepared = $this->prepare( $query, 'DatabaseBase::safeQuery' );
00989 
00990                 if ( !is_array( $args ) ) {
00991                         # Pull the var args
00992                         $args = func_get_args();
00993                         array_shift( $args );
00994                 }
00995 
00996                 $retval = $this->execute( $prepared, $args );
00997                 $this->freePrepared( $prepared );
00998 
00999                 return $retval;
01000         }
01001 
01009         function fillPrepared( $preparedQuery, $args ) {
01010                 reset( $args );
01011                 $this->preparedArgs =& $args;
01012 
01013                 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01014                         array( &$this, 'fillPreparedArg' ), $preparedQuery );
01015         }
01016 
01025         function fillPreparedArg( $matches ) {
01026                 switch( $matches[1] ) {
01027                         case '\\?': return '?';
01028                         case '\\!': return '!';
01029                         case '\\&': return '&';
01030                 }
01031 
01032                 list( /* $n */ , $arg ) = each( $this->preparedArgs );
01033 
01034                 switch( $matches[1] ) {
01035                         case '?': return $this->addQuotes( $arg );
01036                         case '!': return $arg;
01037                         case '&':
01038                                 # return $this->addQuotes( file_get_contents( $arg ) );
01039                                 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
01040                         default:
01041                                 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
01042                 }
01043         }
01044 
01052         function freeResult( $res ) {
01053         }
01054 
01071         function set( $table, $var, $value, $cond, $fname = 'DatabaseBase::set' ) {
01072                 $table = $this->tableName( $table );
01073                 $sql = "UPDATE $table SET $var = '" .
01074                   $this->strencode( $value ) . "' WHERE ($cond)";
01075 
01076                 return (bool)$this->query( $sql, $fname );
01077         }
01078 
01096         function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
01097                 $options = array() )
01098         {
01099                 if ( !is_array( $options ) ) {
01100                         $options = array( $options );
01101                 }
01102 
01103                 $options['LIMIT'] = 1;
01104 
01105                 $res = $this->select( $table, $var, $cond, $fname, $options );
01106 
01107                 if ( $res === false || !$this->numRows( $res ) ) {
01108                         return false;
01109                 }
01110 
01111                 $row = $this->fetchRow( $res );
01112 
01113                 if ( $row !== false ) {
01114                         return reset( $row );
01115                 } else {
01116                         return false;
01117                 }
01118         }
01119 
01129         function makeSelectOptions( $options ) {
01130                 $preLimitTail = $postLimitTail = '';
01131                 $startOpts = '';
01132 
01133                 $noKeyOptions = array();
01134 
01135                 foreach ( $options as $key => $option ) {
01136                         if ( is_numeric( $key ) ) {
01137                                 $noKeyOptions[$option] = true;
01138                         }
01139                 }
01140 
01141                 if ( isset( $options['GROUP BY'] ) ) {
01142                         $gb = is_array( $options['GROUP BY'] )
01143                                 ? implode( ',', $options['GROUP BY'] )
01144                                 : $options['GROUP BY'];
01145                         $preLimitTail .= " GROUP BY {$gb}";
01146                 }
01147 
01148                 if ( isset( $options['HAVING'] ) ) {
01149                         $preLimitTail .= " HAVING {$options['HAVING']}";
01150                 }
01151 
01152                 if ( isset( $options['ORDER BY'] ) ) {
01153                         $ob = is_array( $options['ORDER BY'] )
01154                                 ? implode( ',', $options['ORDER BY'] )
01155                                 : $options['ORDER BY'];
01156                         $preLimitTail .= " ORDER BY {$ob}";
01157                 }
01158 
01159                 // if (isset($options['LIMIT'])) {
01160                 //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
01161                 //              isset($options['OFFSET']) ? $options['OFFSET']
01162                 //              : false);
01163                 // }
01164 
01165                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01166                         $postLimitTail .= ' FOR UPDATE';
01167                 }
01168 
01169                 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01170                         $postLimitTail .= ' LOCK IN SHARE MODE';
01171                 }
01172 
01173                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01174                         $startOpts .= 'DISTINCT';
01175                 }
01176 
01177                 # Various MySQL extensions
01178                 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01179                         $startOpts .= ' /*! STRAIGHT_JOIN */';
01180                 }
01181 
01182                 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01183                         $startOpts .= ' HIGH_PRIORITY';
01184                 }
01185 
01186                 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01187                         $startOpts .= ' SQL_BIG_RESULT';
01188                 }
01189 
01190                 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01191                         $startOpts .= ' SQL_BUFFER_RESULT';
01192                 }
01193 
01194                 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01195                         $startOpts .= ' SQL_SMALL_RESULT';
01196                 }
01197 
01198                 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01199                         $startOpts .= ' SQL_CALC_FOUND_ROWS';
01200                 }
01201 
01202                 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01203                         $startOpts .= ' SQL_CACHE';
01204                 }
01205 
01206                 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01207                         $startOpts .= ' SQL_NO_CACHE';
01208                 }
01209 
01210                 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
01211                         $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01212                 } else {
01213                         $useIndex = '';
01214                 }
01215 
01216                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01217         }
01218 
01354         function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01355                 $options = array(), $join_conds = array() ) {
01356                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01357 
01358                 return $this->query( $sql, $fname );
01359         }
01360 
01375         function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
01376                 if ( is_array( $vars ) ) {
01377                         $vars = implode( ',', $vars );
01378                 }
01379 
01380                 $options = (array)$options;
01381 
01382                 if ( is_array( $table ) ) {
01383                         $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01384                                 ? $options['USE INDEX']
01385                                 : array();
01386                         if ( count( $join_conds ) || count( $useIndex ) ) {
01387                                 $from = ' FROM ' .
01388                                         $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
01389                         } else {
01390                                 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
01391                         }
01392                 } elseif ( $table != '' ) {
01393                         if ( $table[0] == ' ' ) {
01394                                 $from = ' FROM ' . $table;
01395                         } else {
01396                                 $from = ' FROM ' . $this->tableName( $table );
01397                         }
01398                 } else {
01399                         $from = '';
01400                 }
01401 
01402                 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
01403 
01404                 if ( !empty( $conds ) ) {
01405                         if ( is_array( $conds ) ) {
01406                                 $conds = $this->makeList( $conds, LIST_AND );
01407                         }
01408                         $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01409                 } else {
01410                         $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01411                 }
01412 
01413                 if ( isset( $options['LIMIT'] ) ) {
01414                         $sql = $this->limitResult( $sql, $options['LIMIT'],
01415                                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01416                 }
01417                 $sql = "$sql $postLimitTail";
01418 
01419                 if ( isset( $options['EXPLAIN'] ) ) {
01420                         $sql = 'EXPLAIN ' . $sql;
01421                 }
01422 
01423                 return $sql;
01424         }
01425 
01440         function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
01441                 $options = array(), $join_conds = array() )
01442         {
01443                 $options = (array)$options;
01444                 $options['LIMIT'] = 1;
01445                 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01446 
01447                 if ( $res === false ) {
01448                         return false;
01449                 }
01450 
01451                 if ( !$this->numRows( $res ) ) {
01452                         return false;
01453                 }
01454 
01455                 $obj = $this->fetchObject( $res );
01456 
01457                 return $obj;
01458         }
01459 
01480         public function estimateRowCount( $table, $vars = '*', $conds = '',
01481                 $fname = 'DatabaseBase::estimateRowCount', $options = array() )
01482         {
01483                 $rows = 0;
01484                 $res = $this->select ( $table, 'COUNT(*) AS rowcount', $conds, $fname, $options );
01485 
01486                 if ( $res ) {
01487                         $row = $this->fetchRow( $res );
01488                         $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01489                 }
01490 
01491                 return $rows;
01492         }
01493 
01502         static function generalizeSQL( $sql ) {
01503                 # This does the same as the regexp below would do, but in such a way
01504                 # as to avoid crashing php on some large strings.
01505                 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
01506 
01507                 $sql = str_replace ( "\\\\", '', $sql );
01508                 $sql = str_replace ( "\\'", '', $sql );
01509                 $sql = str_replace ( "\\\"", '', $sql );
01510                 $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
01511                 $sql = preg_replace ( '/".*"/s', "'X'", $sql );
01512 
01513                 # All newlines, tabs, etc replaced by single space
01514                 $sql = preg_replace ( '/\s+/', ' ', $sql );
01515 
01516                 # All numbers => N
01517                 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
01518 
01519                 return $sql;
01520         }
01521 
01530         function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
01531                 $info = $this->fieldInfo( $table, $field );
01532 
01533                 return (bool)$info;
01534         }
01535 
01547         function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
01548                 $info = $this->indexInfo( $table, $index, $fname );
01549                 if ( is_null( $info ) ) {
01550                         return null;
01551                 } else {
01552                         return $info !== false;
01553                 }
01554         }
01555 
01564         function tableExists( $table, $fname = __METHOD__ ) {
01565                 $table = $this->tableName( $table );
01566                 $old = $this->ignoreErrors( true );
01567                 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01568                 $this->ignoreErrors( $old );
01569 
01570                 return (bool)$res;
01571         }
01572 
01579         function fieldType( $res, $index ) {
01580                 if ( $res instanceof ResultWrapper ) {
01581                         $res = $res->result;
01582                 }
01583 
01584                 return mysql_field_type( $res, $index );
01585         }
01586 
01595         function indexUnique( $table, $index ) {
01596                 $indexInfo = $this->indexInfo( $table, $index );
01597 
01598                 if ( !$indexInfo ) {
01599                         return null;
01600                 }
01601 
01602                 return !$indexInfo[0]->Non_unique;
01603         }
01604 
01611         function makeInsertOptions( $options ) {
01612                 return implode( ' ', $options );
01613         }
01614 
01648         function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
01649                 # No rows to insert, easy just return now
01650                 if ( !count( $a ) ) {
01651                         return true;
01652                 }
01653 
01654                 $table = $this->tableName( $table );
01655 
01656                 if ( !is_array( $options ) ) {
01657                         $options = array( $options );
01658                 }
01659 
01660                 $options = $this->makeInsertOptions( $options );
01661 
01662                 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01663                         $multi = true;
01664                         $keys = array_keys( $a[0] );
01665                 } else {
01666                         $multi = false;
01667                         $keys = array_keys( $a );
01668                 }
01669 
01670                 $sql = 'INSERT ' . $options .
01671                         " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01672 
01673                 if ( $multi ) {
01674                         $first = true;
01675                         foreach ( $a as $row ) {
01676                                 if ( $first ) {
01677                                         $first = false;
01678                                 } else {
01679                                         $sql .= ',';
01680                                 }
01681                                 $sql .= '(' . $this->makeList( $row ) . ')';
01682                         }
01683                 } else {
01684                         $sql .= '(' . $this->makeList( $a ) . ')';
01685                 }
01686 
01687                 return (bool)$this->query( $sql, $fname );
01688         }
01689 
01696         function makeUpdateOptions( $options ) {
01697                 if ( !is_array( $options ) ) {
01698                         $options = array( $options );
01699                 }
01700 
01701                 $opts = array();
01702 
01703                 if ( in_array( 'LOW_PRIORITY', $options ) ) {
01704                         $opts[] = $this->lowPriorityOption();
01705                 }
01706 
01707                 if ( in_array( 'IGNORE', $options ) ) {
01708                         $opts[] = 'IGNORE';
01709                 }
01710 
01711                 return implode( ' ', $opts );
01712         }
01713 
01737         function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
01738                 $table = $this->tableName( $table );
01739                 $opts = $this->makeUpdateOptions( $options );
01740                 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01741 
01742                 if ( $conds !== array() && $conds !== '*' ) {
01743                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01744                 }
01745 
01746                 return $this->query( $sql, $fname );
01747         }
01748 
01762         function makeList( $a, $mode = LIST_COMMA ) {
01763                 if ( !is_array( $a ) ) {
01764                         throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
01765                 }
01766 
01767                 $first = true;
01768                 $list = '';
01769 
01770                 foreach ( $a as $field => $value ) {
01771                         if ( !$first ) {
01772                                 if ( $mode == LIST_AND ) {
01773                                         $list .= ' AND ';
01774                                 } elseif ( $mode == LIST_OR ) {
01775                                         $list .= ' OR ';
01776                                 } else {
01777                                         $list .= ',';
01778                                 }
01779                         } else {
01780                                 $first = false;
01781                         }
01782 
01783                         if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
01784                                 $list .= "($value)";
01785                         } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
01786                                 $list .= "$value";
01787                         } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
01788                                 if ( count( $value ) == 0 ) {
01789                                         throw new MWException( __METHOD__ . ': empty input' );
01790                                 } elseif ( count( $value ) == 1 ) {
01791                                         // Special-case single values, as IN isn't terribly efficient
01792                                         // Don't necessarily assume the single key is 0; we don't
01793                                         // enforce linear numeric ordering on other arrays here.
01794                                         $value = array_values( $value );
01795                                         $list .= $field . " = " . $this->addQuotes( $value[0] );
01796                                 } else {
01797                                         $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
01798                                 }
01799                         } elseif ( $value === null ) {
01800                                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
01801                                         $list .= "$field IS ";
01802                                 } elseif ( $mode == LIST_SET ) {
01803                                         $list .= "$field = ";
01804                                 }
01805                                 $list .= 'NULL';
01806                         } else {
01807                                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
01808                                         $list .= "$field = ";
01809                                 }
01810                                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
01811                         }
01812                 }
01813 
01814                 return $list;
01815         }
01816 
01827         function makeWhereFrom2d( $data, $baseKey, $subKey ) {
01828                 $conds = array();
01829 
01830                 foreach ( $data as $base => $sub ) {
01831                         if ( count( $sub ) ) {
01832                                 $conds[] = $this->makeList(
01833                                         array( $baseKey => $base, $subKey => array_keys( $sub ) ),
01834                                         LIST_AND );
01835                         }
01836                 }
01837 
01838                 if ( $conds ) {
01839                         return $this->makeList( $conds, LIST_OR );
01840                 } else {
01841                         // Nothing to search for...
01842                         return false;
01843                 }
01844         }
01845 
01854         function bitNot( $field ) {
01855                 return "(~$field)";
01856         }
01857 
01863         function bitAnd( $fieldLeft, $fieldRight ) {
01864                 return "($fieldLeft & $fieldRight)";
01865         }
01866 
01872         function bitOr( $fieldLeft, $fieldRight ) {
01873                 return "($fieldLeft | $fieldRight)";
01874         }
01875 
01885         function selectDB( $db ) {
01886                 # Stub.  Shouldn't cause serious problems if it's not overridden, but
01887                 # if your database engine supports a concept similar to MySQL's
01888                 # databases you may as well.
01889                 $this->mDBname = $db;
01890                 return true;
01891         }
01892 
01896         function getDBname() {
01897                 return $this->mDBname;
01898         }
01899 
01903         function getServer() {
01904                 return $this->mServer;
01905         }
01906 
01924         function tableName( $name, $format = 'quoted' ) {
01925                 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
01926                 # Skip the entire process when we have a string quoted on both ends.
01927                 # Note that we check the end so that we will still quote any use of
01928                 # use of `database`.table. But won't break things if someone wants
01929                 # to query a database table with a dot in the name.
01930                 if ( $this->isQuotedIdentifier( $name ) ) {
01931                         return $name;
01932                 }
01933 
01934                 # Lets test for any bits of text that should never show up in a table
01935                 # name. Basically anything like JOIN or ON which are actually part of
01936                 # SQL queries, but may end up inside of the table value to combine
01937                 # sql. Such as how the API is doing.
01938                 # Note that we use a whitespace test rather than a \b test to avoid
01939                 # any remote case where a word like on may be inside of a table name
01940                 # surrounded by symbols which may be considered word breaks.
01941                 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
01942                         return $name;
01943                 }
01944 
01945                 # Split database and table into proper variables.
01946                 # We reverse the explode so that database.table and table both output
01947                 # the correct table.
01948                 $dbDetails = array_reverse( explode( '.', $name, 2 ) );
01949                 if ( isset( $dbDetails[1] ) ) {
01950                         list( $table, $database ) = $dbDetails;
01951                 } else {
01952                         list( $table ) = $dbDetails;
01953                 }
01954                 $prefix = $this->mTablePrefix; # Default prefix
01955 
01956                 # A database name has been specified in input. We don't want any
01957                 # prefixes added.
01958                 if ( isset( $database ) ) {
01959                         $prefix = '';
01960                 }
01961 
01962                 # Note that we use the long format because php will complain in in_array if
01963                 # the input is not an array, and will complain in is_array if it is not set.
01964                 if ( !isset( $database ) # Don't use shared database if pre selected.
01965                  && isset( $wgSharedDB ) # We have a shared database
01966                  && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
01967                  && isset( $wgSharedTables )
01968                  && is_array( $wgSharedTables )
01969                  && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
01970                         $database = $wgSharedDB;
01971                         $prefix   = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
01972                 }
01973 
01974                 # Quote the $database and $table and apply the prefix if not quoted.
01975                 if ( isset( $database ) ) {
01976                         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
01977                                 $database = $this->addIdentifierQuotes( $database );
01978                         }
01979                 }
01980 
01981                 $table = "{$prefix}{$table}";
01982                 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) {
01983                         $table = $this->addIdentifierQuotes( "{$table}" );
01984                 }
01985 
01986                 # Merge our database and table into our final table name.
01987                 $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
01988 
01989                 return $tableName;
01990         }
01991 
02003         public function tableNames() {
02004                 $inArray = func_get_args();
02005                 $retVal = array();
02006 
02007                 foreach ( $inArray as $name ) {
02008                         $retVal[$name] = $this->tableName( $name );
02009                 }
02010 
02011                 return $retVal;
02012         }
02013 
02025         public function tableNamesN() {
02026                 $inArray = func_get_args();
02027                 $retVal = array();
02028 
02029                 foreach ( $inArray as $name ) {
02030                         $retVal[] = $this->tableName( $name );
02031                 }
02032 
02033                 return $retVal;
02034         }
02035 
02044         public function tableNameWithAlias( $name, $alias = false ) {
02045                 if ( !$alias || $alias == $name ) {
02046                         return $this->tableName( $name );
02047                 } else {
02048                         return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02049                 }
02050         }
02051 
02058         public function tableNamesWithAlias( $tables ) {
02059                 $retval = array();
02060                 foreach ( $tables as $alias => $table ) {
02061                         if ( is_numeric( $alias ) ) {
02062                                 $alias = $table;
02063                         }
02064                         $retval[] = $this->tableNameWithAlias( $table, $alias );
02065                 }
02066                 return $retval;
02067         }
02068 
02078         protected function tableNamesWithUseIndexOrJOIN(
02079                 $tables, $use_index = array(), $join_conds = array()
02080         ) {
02081                 $ret = array();
02082                 $retJOIN = array();
02083                 $use_index = (array)$use_index;
02084                 $join_conds = (array)$join_conds;
02085 
02086                 foreach ( $tables as $alias => $table ) {
02087                         if ( !is_string( $alias ) ) {
02088                                 // No alias? Set it equal to the table name
02089                                 $alias = $table;
02090                         }
02091                         // Is there a JOIN clause for this table?
02092                         if ( isset( $join_conds[$alias] ) ) {
02093                                 list( $joinType, $conds ) = $join_conds[$alias];
02094                                 $tableClause = $joinType;
02095                                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02096                                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02097                                         $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02098                                         if ( $use != '' ) {
02099                                                 $tableClause .= ' ' . $use;
02100                                         }
02101                                 }
02102                                 $on = $this->makeList( (array)$conds, LIST_AND );
02103                                 if ( $on != '' ) {
02104                                         $tableClause .= ' ON (' . $on . ')';
02105                                 }
02106 
02107                                 $retJOIN[] = $tableClause;
02108                         // Is there an INDEX clause for this table?
02109                         } elseif ( isset( $use_index[$alias] ) ) {
02110                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02111                                 $tableClause .= ' ' . $this->useIndexClause(
02112                                         implode( ',', (array)$use_index[$alias] ) );
02113 
02114                                 $ret[] = $tableClause;
02115                         } else {
02116                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02117 
02118                                 $ret[] = $tableClause;
02119                         }
02120                 }
02121 
02122                 // We can't separate explicit JOIN clauses with ',', use ' ' for those
02123                 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02124                 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02125 
02126                 // Compile our final table clause
02127                 return implode( ' ', array( $straightJoins, $otherJoins ) );
02128         }
02129 
02137         function indexName( $index ) {
02138                 // Backwards-compatibility hack
02139                 $renamed = array(
02140                         'ar_usertext_timestamp' => 'usertext_timestamp',
02141                         'un_user_id'            => 'user_id',
02142                         'un_user_ip'            => 'user_ip',
02143                 );
02144 
02145                 if ( isset( $renamed[$index] ) ) {
02146                         return $renamed[$index];
02147                 } else {
02148                         return $index;
02149                 }
02150         }
02151 
02160         function addQuotes( $s ) {
02161                 if ( $s === null ) {
02162                         return 'NULL';
02163                 } else {
02164                         # This will also quote numeric values. This should be harmless,
02165                         # and protects against weird problems that occur when they really
02166                         # _are_ strings such as article titles and string->number->string
02167                         # conversion is not 1:1.
02168                         return "'" . $this->strencode( $s ) . "'";
02169                 }
02170         }
02171 
02182         public function addIdentifierQuotes( $s ) {
02183                 return '"' . str_replace( '"', '""', $s ) . '"';
02184         }
02185 
02194         public function isQuotedIdentifier( $name ) {
02195                 return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02196         }
02197 
02208         function quote_ident( $s ) {
02209                 wfDeprecated( __METHOD__, '1.18' );
02210                 return $this->addIdentifierQuotes( $s );
02211         }
02212 
02223         public function escapeLike( $s ) {
02224                 wfDeprecated( __METHOD__, '1.17' );
02225                 return $this->escapeLikeInternal( $s );
02226         }
02227 
02232         protected function escapeLikeInternal( $s ) {
02233                 $s = str_replace( '\\', '\\\\', $s );
02234                 $s = $this->strencode( $s );
02235                 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
02236 
02237                 return $s;
02238         }
02239 
02252         function buildLike() {
02253                 $params = func_get_args();
02254 
02255                 if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02256                         $params = $params[0];
02257                 }
02258 
02259                 $s = '';
02260 
02261                 foreach ( $params as $value ) {
02262                         if ( $value instanceof LikeMatch ) {
02263                                 $s .= $value->toString();
02264                         } else {
02265                                 $s .= $this->escapeLikeInternal( $value );
02266                         }
02267                 }
02268 
02269                 return " LIKE '" . $s . "' ";
02270         }
02271 
02277         function anyChar() {
02278                 return new LikeMatch( '_' );
02279         }
02280 
02286         function anyString() {
02287                 return new LikeMatch( '%' );
02288         }
02289 
02301         function nextSequenceValue( $seqName ) {
02302                 return null;
02303         }
02304 
02315         function useIndexClause( $index ) {
02316                 return '';
02317         }
02318 
02341         function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
02342                 $quotedTable = $this->tableName( $table );
02343 
02344                 if ( count( $rows ) == 0 ) {
02345                         return;
02346                 }
02347 
02348                 # Single row case
02349                 if ( !is_array( reset( $rows ) ) ) {
02350                         $rows = array( $rows );
02351                 }
02352 
02353                 foreach( $rows as $row ) {
02354                         # Delete rows which collide
02355                         if ( $uniqueIndexes ) {
02356                                 $sql = "DELETE FROM $quotedTable WHERE ";
02357                                 $first = true;
02358                                 foreach ( $uniqueIndexes as $index ) {
02359                                         if ( $first ) {
02360                                                 $first = false;
02361                                                 $sql .= '( ';
02362                                         } else {
02363                                                 $sql .= ' ) OR ( ';
02364                                         }
02365                                         if ( is_array( $index ) ) {
02366                                                 $first2 = true;
02367                                                 foreach ( $index as $col ) {
02368                                                         if ( $first2 ) {
02369                                                                 $first2 = false;
02370                                                         } else {
02371                                                                 $sql .= ' AND ';
02372                                                         }
02373                                                         $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02374                                                 }
02375                                         } else {
02376                                                 $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02377                                         }
02378                                 }
02379                                 $sql .= ' )';
02380                                 $this->query( $sql, $fname );
02381                         }
02382 
02383                         # Now insert the row
02384                         $this->insert( $table, $row );
02385                 }
02386         }
02387 
02398         protected function nativeReplace( $table, $rows, $fname ) {
02399                 $table = $this->tableName( $table );
02400 
02401                 # Single row case
02402                 if ( !is_array( reset( $rows ) ) ) {
02403                         $rows = array( $rows );
02404                 }
02405 
02406                 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02407                 $first = true;
02408 
02409                 foreach ( $rows as $row ) {
02410                         if ( $first ) {
02411                                 $first = false;
02412                         } else {
02413                                 $sql .= ',';
02414                         }
02415 
02416                         $sql .= '(' . $this->makeList( $row ) . ')';
02417                 }
02418 
02419                 return $this->query( $sql, $fname );
02420         }
02421 
02442         function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02443                 $fname = 'DatabaseBase::deleteJoin' )
02444         {
02445                 if ( !$conds ) {
02446                         throw new DBUnexpectedError( $this,
02447                                 'DatabaseBase::deleteJoin() called with empty $conds' );
02448                 }
02449 
02450                 $delTable = $this->tableName( $delTable );
02451                 $joinTable = $this->tableName( $joinTable );
02452                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02453                 if ( $conds != '*' ) {
02454                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02455                 }
02456                 $sql .= ')';
02457 
02458                 $this->query( $sql, $fname );
02459         }
02460 
02469         function textFieldSize( $table, $field ) {
02470                 $table = $this->tableName( $table );
02471                 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02472                 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02473                 $row = $this->fetchObject( $res );
02474 
02475                 $m = array();
02476 
02477                 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02478                         $size = $m[1];
02479                 } else {
02480                         $size = -1;
02481                 }
02482 
02483                 return $size;
02484         }
02485 
02494         function lowPriorityOption() {
02495                 return '';
02496         }
02497 
02508         function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
02509                 if ( !$conds ) {
02510                         throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02511                 }
02512 
02513                 $table = $this->tableName( $table );
02514                 $sql = "DELETE FROM $table";
02515 
02516                 if ( $conds != '*' ) {
02517                         if ( is_array( $conds ) ) {
02518                                 $conds = $this->makeList( $conds, LIST_AND );
02519                         }
02520                         $sql .= ' WHERE ' . $conds;
02521                 }
02522 
02523                 return $this->query( $sql, $fname );
02524         }
02525 
02552         function insertSelect( $destTable, $srcTable, $varMap, $conds,
02553                 $fname = 'DatabaseBase::insertSelect',
02554                 $insertOptions = array(), $selectOptions = array() )
02555         {
02556                 $destTable = $this->tableName( $destTable );
02557 
02558                 if ( is_array( $insertOptions ) ) {
02559                         $insertOptions = implode( ' ', $insertOptions );
02560                 }
02561 
02562                 if ( !is_array( $selectOptions ) ) {
02563                         $selectOptions = array( $selectOptions );
02564                 }
02565 
02566                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
02567 
02568                 if ( is_array( $srcTable ) ) {
02569                         $srcTable =  implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
02570                 } else {
02571                         $srcTable = $this->tableName( $srcTable );
02572                 }
02573 
02574                 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
02575                         " SELECT $startOpts " . implode( ',', $varMap ) .
02576                         " FROM $srcTable $useIndex ";
02577 
02578                 if ( $conds != '*' ) {
02579                         if ( is_array( $conds ) ) {
02580                                 $conds = $this->makeList( $conds, LIST_AND );
02581                         }
02582                         $sql .= " WHERE $conds";
02583                 }
02584 
02585                 $sql .= " $tailOpts";
02586 
02587                 return $this->query( $sql, $fname );
02588         }
02589 
02610         function limitResult( $sql, $limit, $offset = false ) {
02611                 if ( !is_numeric( $limit ) ) {
02612                         throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
02613                 }
02614 
02615                 return "$sql LIMIT "
02616                                 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
02617                                 . "{$limit} ";
02618         }
02619 
02625         function limitResultForUpdate( $sql, $num ) {
02626                 return $this->limitResult( $sql, $num, 0 );
02627         }
02628 
02634         function unionSupportsOrderAndLimit() {
02635                 return true; // True for almost every DB supported
02636         }
02637 
02646         function unionQueries( $sqls, $all ) {
02647                 $glue = $all ? ') UNION ALL (' : ') UNION (';
02648                 return '(' . implode( $glue, $sqls ) . ')';
02649         }
02650 
02660         function conditional( $cond, $trueVal, $falseVal ) {
02661                 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
02662         }
02663 
02674         function strreplace( $orig, $old, $new ) {
02675                 return "REPLACE({$orig}, {$old}, {$new})";
02676         }
02677 
02684         function getServerUptime() {
02685                 return 0;
02686         }
02687 
02694         function wasDeadlock() {
02695                 return false;
02696         }
02697 
02704         function wasLockTimeout() {
02705                 return false;
02706         }
02707 
02715         function wasErrorReissuable() {
02716                 return false;
02717         }
02718 
02725         function wasReadOnlyError() {
02726                 return false;
02727         }
02728 
02747         function deadlockLoop() {
02748 
02749                 $this->begin( __METHOD__ );
02750                 $args = func_get_args();
02751                 $function = array_shift( $args );
02752                 $oldIgnore = $this->ignoreErrors( true );
02753                 $tries = DEADLOCK_TRIES;
02754 
02755                 if ( is_array( $function ) ) {
02756                         $fname = $function[0];
02757                 } else {
02758                         $fname = $function;
02759                 }
02760 
02761                 do {
02762                         $retVal = call_user_func_array( $function, $args );
02763                         $error = $this->lastError();
02764                         $errno = $this->lastErrno();
02765                         $sql = $this->lastQuery();
02766 
02767                         if ( $errno ) {
02768                                 if ( $this->wasDeadlock() ) {
02769                                         # Retry
02770                                         usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
02771                                 } else {
02772                                         $this->reportQueryError( $error, $errno, $sql, $fname );
02773                                 }
02774                         }
02775                 } while ( $this->wasDeadlock() && --$tries > 0 );
02776 
02777                 $this->ignoreErrors( $oldIgnore );
02778 
02779                 if ( $tries <= 0 ) {
02780                         $this->rollback( __METHOD__ );
02781                         $this->reportQueryError( $error, $errno, $sql, $fname );
02782                         return false;
02783                 } else {
02784                         $this->commit( __METHOD__ );
02785                         return $retVal;
02786                 }
02787         }
02788 
02800         function masterPosWait( DBMasterPos $pos, $timeout ) {
02801                 wfProfileIn( __METHOD__ );
02802 
02803                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02804                         $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
02805 
02806                         if ( $wait > $timeout * 1e6 ) {
02807                                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
02808                                 wfProfileOut( __METHOD__ );
02809                                 return -1;
02810                         } elseif ( $wait > 0 ) {
02811                                 wfDebug( "Fake slave waiting $wait us\n" );
02812                                 usleep( $wait );
02813                                 wfProfileOut( __METHOD__ );
02814                                 return 1;
02815                         } else {
02816                                 wfDebug( "Fake slave up to date ($wait us)\n" );
02817                                 wfProfileOut( __METHOD__ );
02818                                 return 0;
02819                         }
02820                 }
02821 
02822                 wfProfileOut( __METHOD__ );
02823 
02824                 # Real waits are implemented in the subclass.
02825                 return 0;
02826         }
02827 
02833         function getSlavePos() {
02834                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02835                         $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
02836                         wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
02837                         return $pos;
02838                 } else {
02839                         # Stub
02840                         return false;
02841                 }
02842         }
02843 
02849         function getMasterPos() {
02850                 if ( $this->mFakeMaster ) {
02851                         return new MySQLMasterPos( 'fake', microtime( true ) );
02852                 } else {
02853                         return false;
02854                 }
02855         }
02856 
02862         function begin( $fname = 'DatabaseBase::begin' ) {
02863                 $this->query( 'BEGIN', $fname );
02864                 $this->mTrxLevel = 1;
02865         }
02866 
02872         function commit( $fname = 'DatabaseBase::commit' ) {
02873                 if ( $this->mTrxLevel ) {
02874                         $this->query( 'COMMIT', $fname );
02875                         $this->mTrxLevel = 0;
02876                 }
02877         }
02878 
02885         function rollback( $fname = 'DatabaseBase::rollback' ) {
02886                 if ( $this->mTrxLevel ) {
02887                         $this->query( 'ROLLBACK', $fname, true );
02888                         $this->mTrxLevel = 0;
02889                 }
02890         }
02891 
02906         function duplicateTableStructure( $oldName, $newName, $temporary = false,
02907                 $fname = 'DatabaseBase::duplicateTableStructure' )
02908         {
02909                 throw new MWException(
02910                         'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
02911         }
02912 
02919         function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
02920                 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
02921         }
02922 
02934         function timestamp( $ts = 0 ) {
02935                 return wfTimestamp( TS_MW, $ts );
02936         }
02937 
02951         function timestampOrNull( $ts = null ) {
02952                 if ( is_null( $ts ) ) {
02953                         return null;
02954                 } else {
02955                         return $this->timestamp( $ts );
02956                 }
02957         }
02958 
02974         function resultObject( $result ) {
02975                 if ( empty( $result ) ) {
02976                         return false;
02977                 } elseif ( $result instanceof ResultWrapper ) {
02978                         return $result;
02979                 } elseif ( $result === true ) {
02980                         // Successful write query
02981                         return $result;
02982                 } else {
02983                         return new ResultWrapper( $this, $result );
02984                 }
02985         }
02986 
02995         function aggregateValue ( $valuedata, $valuename = 'value' ) {
02996                 return $valuename;
02997         }
02998 
03004         function ping() {
03005                 # Stub.  Not essential to override.
03006                 return true;
03007         }
03008 
03018         function getLag() {
03019                 return intval( $this->mFakeSlaveLag );
03020         }
03021 
03027         function maxListLen() {
03028                 return 0;
03029         }
03030 
03039         function encodeBlob( $b ) {
03040                 return $b;
03041         }
03042 
03050         function decodeBlob( $b ) {
03051                 return $b;
03052         }
03053 
03061         public function setTimeout( $timeout ) {
03062                 wfDeprecated( __METHOD__, '1.19' );
03063                 $this->setSessionOptions( array( 'connTimeout' => $timeout ) );
03064         }
03065 
03076         public function setSessionOptions( array $options ) {}
03077 
03091         function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
03092                 wfSuppressWarnings();
03093                 $fp = fopen( $filename, 'r' );
03094                 wfRestoreWarnings();
03095 
03096                 if ( false === $fp ) {
03097                         throw new MWException( "Could not open \"{$filename}\".\n" );
03098                 }
03099 
03100                 if ( !$fname ) {
03101                         $fname = __METHOD__ . "( $filename )";
03102                 }
03103 
03104                 try {
03105                         $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
03106                 }
03107                 catch ( MWException $e ) {
03108                         fclose( $fp );
03109                         throw $e;
03110                 }
03111 
03112                 fclose( $fp );
03113 
03114                 return $error;
03115         }
03116 
03125         public function patchPath( $patch ) {
03126                 global $IP;
03127 
03128                 $dbType = $this->getType();
03129                 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03130                         return "$IP/maintenance/$dbType/archives/$patch";
03131                 } else {
03132                         return "$IP/maintenance/archives/$patch";
03133                 }
03134         }
03135 
03143         function setSchemaVars( $vars ) {
03144                 $this->mSchemaVars = $vars;
03145         }
03146 
03160         public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03161                 $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
03162         {
03163                 $cmd = '';
03164 
03165                 while ( !feof( $fp ) ) {
03166                         if ( $lineCallback ) {
03167                                 call_user_func( $lineCallback );
03168                         }
03169 
03170                         $line = trim( fgets( $fp ) );
03171 
03172                         if ( $line == '' ) {
03173                                 continue;
03174                         }
03175 
03176                         if ( '-' == $line[0] && '-' == $line[1] ) {
03177                                 continue;
03178                         }
03179 
03180                         if ( $cmd != '' ) {
03181                                 $cmd .= ' ';
03182                         }
03183 
03184                         $done = $this->streamStatementEnd( $cmd, $line );
03185 
03186                         $cmd .= "$line\n";
03187 
03188                         if ( $done || feof( $fp ) ) {
03189                                 $cmd = $this->replaceVars( $cmd );
03190                                 if ( $inputCallback ) {
03191                                         call_user_func( $inputCallback, $cmd );
03192                                 }
03193                                 $res = $this->query( $cmd, $fname );
03194 
03195                                 if ( $resultCallback ) {
03196                                         call_user_func( $resultCallback, $res, $this );
03197                                 }
03198 
03199                                 if ( false === $res ) {
03200                                         $err = $this->lastError();
03201                                         return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03202                                 }
03203 
03204                                 $cmd = '';
03205                         }
03206                 }
03207 
03208                 return true;
03209         }
03210 
03218         public function streamStatementEnd( &$sql, &$newLine ) {
03219                 if ( $this->delimiter ) {
03220                         $prev = $newLine;
03221                         $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
03222                         if ( $newLine != $prev ) {
03223                                 return true;
03224                         }
03225                 }
03226                 return false;
03227         }
03228 
03246         protected function replaceSchemaVars( $ins ) {
03247                 $vars = $this->getSchemaVars();
03248                 foreach ( $vars as $var => $value ) {
03249                         // replace '{$var}'
03250                         $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
03251                         // replace `{$var}`
03252                         $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
03253                         // replace /*$var*/
03254                         $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins );
03255                 }
03256                 return $ins;
03257         }
03258 
03266         protected function replaceVars( $ins ) {
03267                 $ins = $this->replaceSchemaVars( $ins );
03268 
03269                 // Table prefixes
03270                 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
03271                         array( $this, 'tableNameCallback' ), $ins );
03272 
03273                 // Index names
03274                 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
03275                         array( $this, 'indexNameCallback' ), $ins );
03276 
03277                 return $ins;
03278         }
03279 
03286         protected function getSchemaVars() {
03287                 if ( $this->mSchemaVars ) {
03288                         return $this->mSchemaVars;
03289                 } else {
03290                         return $this->getDefaultSchemaVars();
03291                 }
03292         }
03293 
03302         protected function getDefaultSchemaVars() {
03303                 return array();
03304         }
03305 
03313         protected function tableNameCallback( $matches ) {
03314                 return $this->tableName( $matches[1] );
03315         }
03316 
03324         protected function indexNameCallback( $matches ) {
03325                 return $this->indexName( $matches[1] );
03326         }
03327 
03333         function buildConcat( $stringList ) {
03334                 return 'CONCAT(' . implode( ',', $stringList ) . ')';
03335         }
03336 
03348         public function lock( $lockName, $method, $timeout = 5 ) {
03349                 return true;
03350         }
03351 
03362         public function unlock( $lockName, $method ) {
03363                 return true;
03364         }
03365 
03376         public function lockTables( $read, $write, $method, $lowPriority = true ) {
03377                 return true;
03378         }
03379 
03387         public function unlockTables( $method ) {
03388                 return true;
03389         }
03390 
03398         public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
03399                 if( !$this->tableExists( $tableName, $fName ) ) {
03400                         return false;
03401                 }
03402                 $sql = "DROP TABLE " . $this->tableName( $tableName );
03403                 if( $this->cascadingDeletes() ) {
03404                         $sql .= " CASCADE";
03405                 }
03406                 return $this->query( $sql, $fName );
03407         }
03408 
03415         public function getSearchEngine() {
03416                 return 'SearchEngineDummy';
03417         }
03418 
03426         public function getInfinity() {
03427                 return 'infinity';
03428         }
03429 
03436         public function encodeExpiry( $expiry ) {
03437                 if ( $expiry == '' || $expiry == $this->getInfinity() ) {
03438                         return $this->getInfinity();
03439                 } else {
03440                         return $this->timestamp( $expiry );
03441                 }
03442         }
03443 
03453         public function setBigSelects( $value = true ) {
03454                 // no-op
03455         }
03456 }