MediaWiki  REL1_22
Database.php
Go to the documentation of this file.
00001 <?php
00034 interface DatabaseType {
00040     function getType();
00041 
00052     function open( $server, $user, $password, $dbName );
00053 
00064     function fetchObject( $res );
00065 
00075     function fetchRow( $res );
00076 
00083     function numRows( $res );
00084 
00092     function numFields( $res );
00093 
00102     function fieldName( $res, $n );
00103 
00116     function insertId();
00117 
00125     function dataSeek( $res, $row );
00126 
00133     function lastErrno();
00134 
00141     function lastError();
00142 
00152     function fieldInfo( $table, $field );
00153 
00161     function indexInfo( $table, $index, $fname = __METHOD__ );
00162 
00169     function affectedRows();
00170 
00177     function strencode( $s );
00178 
00187     function getSoftwareLink();
00188 
00195     function getServerVersion();
00196 
00204     function getServerInfo();
00205 }
00206 
00211 interface IDatabase {}
00212 
00217 abstract class DatabaseBase implements IDatabase, DatabaseType {
00219     const DEADLOCK_TRIES = 4;
00221     const DEADLOCK_DELAY_MIN = 500000;
00223     const DEADLOCK_DELAY_MAX = 1500000;
00224 
00225 # ------------------------------------------------------------------------------
00226 # Variables
00227 # ------------------------------------------------------------------------------
00228 
00229     protected $mLastQuery = '';
00230     protected $mDoneWrites = false;
00231     protected $mPHPError = false;
00232 
00233     protected $mServer, $mUser, $mPassword, $mDBname;
00234 
00235     protected $mConn = null;
00236     protected $mOpened = false;
00237 
00239     protected $mTrxIdleCallbacks = array();
00241     protected $mTrxPreCommitCallbacks = array();
00242 
00243     protected $mTablePrefix;
00244     protected $mFlags;
00245     protected $mForeign;
00246     protected $mTrxLevel = 0;
00247     protected $mErrorCount = 0;
00248     protected $mLBInfo = array();
00249     protected $mFakeSlaveLag = null, $mFakeMaster = false;
00250     protected $mDefaultBigSelects = null;
00251     protected $mSchemaVars = false;
00252 
00253     protected $preparedArgs;
00254 
00255     protected $htmlErrors;
00256 
00257     protected $delimiter = ';';
00258 
00266     private $mTrxFname = null;
00267 
00274     private $mTrxDoneWrites = false;
00275 
00282     private $mTrxAutomatic = false;
00283 
00288     protected $fileHandle = null;
00289 
00294     protected $allViews = null;
00295 
00296 # ------------------------------------------------------------------------------
00297 # Accessors
00298 # ------------------------------------------------------------------------------
00299     # These optionally set a variable and return the previous state
00300 
00308     public function getServerInfo() {
00309         return $this->getServerVersion();
00310     }
00311 
00315     public function getDelimiter() {
00316         return $this->delimiter;
00317     }
00318 
00328     public function debug( $debug = null ) {
00329         return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00330     }
00331 
00354     public function bufferResults( $buffer = null ) {
00355         if ( is_null( $buffer ) ) {
00356             return !(bool)( $this->mFlags & DBO_NOBUFFER );
00357         } else {
00358             return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00359         }
00360     }
00361 
00375     public function ignoreErrors( $ignoreErrors = null ) {
00376         return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00377     }
00378 
00388     public function trxLevel( $level = null ) {
00389         return wfSetVar( $this->mTrxLevel, $level );
00390     }
00391 
00397     public function errorCount( $count = null ) {
00398         return wfSetVar( $this->mErrorCount, $count );
00399     }
00400 
00406     public function tablePrefix( $prefix = null ) {
00407         return wfSetVar( $this->mTablePrefix, $prefix );
00408     }
00409 
00415     public function setFileHandle( $fh ) {
00416         $this->fileHandle = $fh;
00417     }
00418 
00428     public function getLBInfo( $name = null ) {
00429         if ( is_null( $name ) ) {
00430             return $this->mLBInfo;
00431         } else {
00432             if ( array_key_exists( $name, $this->mLBInfo ) ) {
00433                 return $this->mLBInfo[$name];
00434             } else {
00435                 return null;
00436             }
00437         }
00438     }
00439 
00448     public function setLBInfo( $name, $value = null ) {
00449         if ( is_null( $value ) ) {
00450             $this->mLBInfo = $name;
00451         } else {
00452             $this->mLBInfo[$name] = $value;
00453         }
00454     }
00455 
00461     public function setFakeSlaveLag( $lag ) {
00462         $this->mFakeSlaveLag = $lag;
00463     }
00464 
00470     public function setFakeMaster( $enabled = true ) {
00471         $this->mFakeMaster = $enabled;
00472     }
00473 
00479     public function cascadingDeletes() {
00480         return false;
00481     }
00482 
00488     public function cleanupTriggers() {
00489         return false;
00490     }
00491 
00498     public function strictIPs() {
00499         return false;
00500     }
00501 
00507     public function realTimestamps() {
00508         return false;
00509     }
00510 
00516     public function implicitGroupby() {
00517         return true;
00518     }
00519 
00526     public function implicitOrderby() {
00527         return true;
00528     }
00529 
00536     public function searchableIPs() {
00537         return false;
00538     }
00539 
00545     public function functionalIndexes() {
00546         return false;
00547     }
00548 
00553     public function lastQuery() {
00554         return $this->mLastQuery;
00555     }
00556 
00563     public function doneWrites() {
00564         return $this->mDoneWrites;
00565     }
00566 
00573     public function writesOrCallbacksPending() {
00574         return $this->mTrxLevel && (
00575             $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
00576         );
00577     }
00578 
00583     public function isOpen() {
00584         return $this->mOpened;
00585     }
00586 
00598     public function setFlag( $flag ) {
00599         global $wgDebugDBTransactions;
00600         $this->mFlags |= $flag;
00601         if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) {
00602             wfDebug( "Implicit transactions are now  disabled.\n" );
00603         }
00604     }
00605 
00611     public function clearFlag( $flag ) {
00612         global $wgDebugDBTransactions;
00613         $this->mFlags &= ~$flag;
00614         if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00615             wfDebug( "Implicit transactions are now disabled.\n" );
00616         }
00617     }
00618 
00625     public function getFlag( $flag ) {
00626         return !!( $this->mFlags & $flag );
00627     }
00628 
00636     public function getProperty( $name ) {
00637         return $this->$name;
00638     }
00639 
00643     public function getWikiID() {
00644         if ( $this->mTablePrefix ) {
00645             return "{$this->mDBname}-{$this->mTablePrefix}";
00646         } else {
00647             return $this->mDBname;
00648         }
00649     }
00650 
00656     public function getSchemaPath() {
00657         global $IP;
00658         if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
00659             return "$IP/maintenance/" . $this->getType() . "/tables.sql";
00660         } else {
00661             return "$IP/maintenance/tables.sql";
00662         }
00663     }
00664 
00665 # ------------------------------------------------------------------------------
00666 # Other functions
00667 # ------------------------------------------------------------------------------
00668 
00691     function __construct( $server = false, $user = false, $password = false, $dbName = false,
00692         $flags = 0, $tablePrefix = 'get from global', $foreign = false
00693     ) {
00694         global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
00695 
00696         $this->mFlags = $flags;
00697 
00698         if ( $this->mFlags & DBO_DEFAULT ) {
00699             if ( $wgCommandLineMode ) {
00700                 $this->mFlags &= ~DBO_TRX;
00701                 if ( $wgDebugDBTransactions ) {
00702                     wfDebug( "Implicit transaction open disabled.\n" );
00703                 }
00704             } else {
00705                 $this->mFlags |= DBO_TRX;
00706                 if ( $wgDebugDBTransactions ) {
00707                     wfDebug( "Implicit transaction open enabled.\n" );
00708                 }
00709             }
00710         }
00711 
00713         if ( $tablePrefix == 'get from global' ) {
00714             $this->mTablePrefix = $wgDBprefix;
00715         } else {
00716             $this->mTablePrefix = $tablePrefix;
00717         }
00718 
00719         $this->mForeign = $foreign;
00720 
00721         if ( $user ) {
00722             $this->open( $server, $user, $password, $dbName );
00723         }
00724     }
00725 
00731     public function __sleep() {
00732         throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
00733     }
00734 
00756     final public static function factory( $dbType, $p = array() ) {
00757         $canonicalDBTypes = array(
00758             'mysql'    => array( 'mysqli', 'mysql' ),
00759             'postgres' => array(),
00760             'sqlite'   => array(),
00761             'oracle'   => array(),
00762             'mssql'    => array(),
00763         );
00764 
00765         $driver = false;
00766         $dbType = strtolower( $dbType );
00767         if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
00768             $possibleDrivers = $canonicalDBTypes[$dbType];
00769             if ( !empty( $p['driver'] ) ) {
00770                 if ( in_array( $p['driver'], $possibleDrivers ) ) {
00771                     $driver = $p['driver'];
00772                 } else {
00773                     throw new MWException( __METHOD__ .
00774                         " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
00775                 }
00776             } else {
00777                 foreach ( $possibleDrivers as $posDriver ) {
00778                     if ( extension_loaded( $posDriver ) ) {
00779                         $driver = $posDriver;
00780                         break;
00781                     }
00782                 }
00783             }
00784         } else {
00785             $driver = $dbType;
00786         }
00787         if ( $driver === false ) {
00788             throw new MWException( __METHOD__ .
00789                 " no viable database extension found for type '$dbType'" );
00790         }
00791 
00792         $class = 'Database' . ucfirst( $driver );
00793         if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
00794             return new $class(
00795                 isset( $p['host'] ) ? $p['host'] : false,
00796                 isset( $p['user'] ) ? $p['user'] : false,
00797                 isset( $p['password'] ) ? $p['password'] : false,
00798                 isset( $p['dbname'] ) ? $p['dbname'] : false,
00799                 isset( $p['flags'] ) ? $p['flags'] : 0,
00800                 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
00801                 isset( $p['foreign'] ) ? $p['foreign'] : false
00802             );
00803         } else {
00804             return null;
00805         }
00806     }
00807 
00808     protected function installErrorHandler() {
00809         $this->mPHPError = false;
00810         $this->htmlErrors = ini_set( 'html_errors', '0' );
00811         set_error_handler( array( $this, 'connectionErrorHandler' ) );
00812     }
00813 
00817     protected function restoreErrorHandler() {
00818         restore_error_handler();
00819         if ( $this->htmlErrors !== false ) {
00820             ini_set( 'html_errors', $this->htmlErrors );
00821         }
00822         if ( $this->mPHPError ) {
00823             $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00824             $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
00825             return $error;
00826         } else {
00827             return false;
00828         }
00829     }
00830 
00836     public function connectionErrorHandler( $errno, $errstr ) {
00837         $this->mPHPError = $errstr;
00838     }
00839 
00847     public function close() {
00848         if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
00849             throw new MWException( "Transaction idle callbacks still pending." );
00850         }
00851         $this->mOpened = false;
00852         if ( $this->mConn ) {
00853             if ( $this->trxLevel() ) {
00854                 if ( !$this->mTrxAutomatic ) {
00855                     wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
00856                         " performing implicit commit before closing connection!" );
00857                 }
00858 
00859                 $this->commit( __METHOD__, 'flush' );
00860             }
00861 
00862             $ret = $this->closeConnection();
00863             $this->mConn = false;
00864             return $ret;
00865         } else {
00866             return true;
00867         }
00868     }
00869 
00875     abstract protected function closeConnection();
00876 
00881     function reportConnectionError( $error = 'Unknown error' ) {
00882         $myError = $this->lastError();
00883         if ( $myError ) {
00884             $error = $myError;
00885         }
00886 
00887         # New method
00888         throw new DBConnectionError( $this, $error );
00889     }
00890 
00897     abstract protected function doQuery( $sql );
00898 
00907     public function isWriteQuery( $sql ) {
00908         return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
00909     }
00910 
00933     public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
00934         global $wgUser, $wgDebugDBTransactions;
00935 
00936         $this->mLastQuery = $sql;
00937         if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
00938             # Set a flag indicating that writes have been done
00939             wfDebug( __METHOD__ . ": Writes done: $sql\n" );
00940             $this->mDoneWrites = true;
00941         }
00942 
00943         # Add a comment for easy SHOW PROCESSLIST interpretation
00944         if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
00945             $userName = $wgUser->getName();
00946             if ( mb_strlen( $userName ) > 15 ) {
00947                 $userName = mb_substr( $userName, 0, 15 ) . '...';
00948             }
00949             $userName = str_replace( '/', '', $userName );
00950         } else {
00951             $userName = '';
00952         }
00953 
00954         // Add trace comment to the begin of the sql string, right after the operator.
00955         // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
00956         $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
00957 
00958         # If DBO_TRX is set, start a transaction
00959         if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
00960             $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' )
00961         {
00962             # Avoid establishing transactions for SHOW and SET statements too -
00963             # that would delay transaction initializations to once connection
00964             # is really used by application
00965             $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
00966             if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
00967                 if ( $wgDebugDBTransactions ) {
00968                     wfDebug( "Implicit transaction start.\n" );
00969                 }
00970                 $this->begin( __METHOD__ . " ($fname)" );
00971                 $this->mTrxAutomatic = true;
00972             }
00973         }
00974 
00975         # Keep track of whether the transaction has write queries pending
00976         if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
00977             $this->mTrxDoneWrites = true;
00978             Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname );
00979         }
00980 
00981         $isMaster = !is_null( $this->getLBInfo( 'master' ) );
00982         if ( !Profiler::instance()->isStub() ) {
00983             # generalizeSQL will probably cut down the query to reasonable
00984             # logging size most of the time. The substr is really just a sanity check.
00985             if ( $isMaster ) {
00986                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00987                 $totalProf = 'DatabaseBase::query-master';
00988             } else {
00989                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00990                 $totalProf = 'DatabaseBase::query';
00991             }
00992             wfProfileIn( $totalProf );
00993             wfProfileIn( $queryProf );
00994         }
00995 
00996         if ( $this->debug() ) {
00997             static $cnt = 0;
00998 
00999             $cnt++;
01000             $sqlx = substr( $commentedSql, 0, 500 );
01001             $sqlx = strtr( $sqlx, "\t\n", '  ' );
01002 
01003             $master = $isMaster ? 'master' : 'slave';
01004             wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
01005         }
01006 
01007         $queryId = MWDebug::query( $sql, $fname, $isMaster );
01008 
01009         # Do the query and handle errors
01010         $ret = $this->doQuery( $commentedSql );
01011 
01012         MWDebug::queryTime( $queryId );
01013 
01014         # Try reconnecting if the connection was lost
01015         if ( false === $ret && $this->wasErrorReissuable() ) {
01016             # Transaction is gone, like it or not
01017             $this->mTrxLevel = 0;
01018             $this->mTrxIdleCallbacks = array(); // cancel
01019             $this->mTrxPreCommitCallbacks = array(); // cancel
01020             wfDebug( "Connection lost, reconnecting...\n" );
01021 
01022             if ( $this->ping() ) {
01023                 wfDebug( "Reconnected\n" );
01024                 $sqlx = substr( $commentedSql, 0, 500 );
01025                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
01026                 global $wgRequestTime;
01027                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
01028                 if ( $elapsed < 300 ) {
01029                     # Not a database error to lose a transaction after a minute or two
01030                     wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
01031                 }
01032                 $ret = $this->doQuery( $commentedSql );
01033             } else {
01034                 wfDebug( "Failed\n" );
01035             }
01036         }
01037 
01038         if ( false === $ret ) {
01039             $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
01040         }
01041 
01042         if ( !Profiler::instance()->isStub() ) {
01043             wfProfileOut( $queryProf );
01044             wfProfileOut( $totalProf );
01045         }
01046 
01047         return $this->resultObject( $ret );
01048     }
01049 
01061     public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
01062         # Ignore errors during error handling to avoid infinite recursion
01063         $ignore = $this->ignoreErrors( true );
01064         ++$this->mErrorCount;
01065 
01066         if ( $ignore || $tempIgnore ) {
01067             wfDebug( "SQL ERROR (ignored): $error\n" );
01068             $this->ignoreErrors( $ignore );
01069         } else {
01070             $sql1line = str_replace( "\n", "\\n", $sql );
01071             wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
01072             wfDebug( "SQL ERROR: " . $error . "\n" );
01073             throw new DBQueryError( $this, $error, $errno, $sql, $fname );
01074         }
01075     }
01076 
01091     protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
01092         /* MySQL doesn't support prepared statements (yet), so just
01093            pack up the query for reference. We'll manually replace
01094            the bits later. */
01095         return array( 'query' => $sql, 'func' => $func );
01096     }
01097 
01102     protected function freePrepared( $prepared ) {
01103         /* No-op by default */
01104     }
01105 
01113     public function execute( $prepared, $args = null ) {
01114         if ( !is_array( $args ) ) {
01115             # Pull the var args
01116             $args = func_get_args();
01117             array_shift( $args );
01118         }
01119 
01120         $sql = $this->fillPrepared( $prepared['query'], $args );
01121 
01122         return $this->query( $sql, $prepared['func'] );
01123     }
01124 
01132     public function fillPrepared( $preparedQuery, $args ) {
01133         reset( $args );
01134         $this->preparedArgs =& $args;
01135 
01136         return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01137             array( &$this, 'fillPreparedArg' ), $preparedQuery );
01138     }
01139 
01149     protected function fillPreparedArg( $matches ) {
01150         switch ( $matches[1] ) {
01151             case '\\?':
01152                 return '?';
01153             case '\\!':
01154                 return '!';
01155             case '\\&':
01156                 return '&';
01157         }
01158 
01159         list( /* $n */, $arg ) = each( $this->preparedArgs );
01160 
01161         switch ( $matches[1] ) {
01162             case '?':
01163                 return $this->addQuotes( $arg );
01164             case '!':
01165                 return $arg;
01166             case '&':
01167                 # return $this->addQuotes( file_get_contents( $arg ) );
01168                 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
01169             default:
01170                 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
01171         }
01172     }
01173 
01181     public function freeResult( $res ) {
01182     }
01183 
01201     public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
01202         $options = array()
01203     ) {
01204         if ( !is_array( $options ) ) {
01205             $options = array( $options );
01206         }
01207 
01208         $options['LIMIT'] = 1;
01209 
01210         $res = $this->select( $table, $var, $cond, $fname, $options );
01211 
01212         if ( $res === false || !$this->numRows( $res ) ) {
01213             return false;
01214         }
01215 
01216         $row = $this->fetchRow( $res );
01217 
01218         if ( $row !== false ) {
01219             return reset( $row );
01220         } else {
01221             return false;
01222         }
01223     }
01224 
01234     public function makeSelectOptions( $options ) {
01235         $preLimitTail = $postLimitTail = '';
01236         $startOpts = '';
01237 
01238         $noKeyOptions = array();
01239 
01240         foreach ( $options as $key => $option ) {
01241             if ( is_numeric( $key ) ) {
01242                 $noKeyOptions[$option] = true;
01243             }
01244         }
01245 
01246         $preLimitTail .= $this->makeGroupByWithHaving( $options );
01247 
01248         $preLimitTail .= $this->makeOrderBy( $options );
01249 
01250         // if (isset($options['LIMIT'])) {
01251         //  $tailOpts .= $this->limitResult('', $options['LIMIT'],
01252         //      isset($options['OFFSET']) ? $options['OFFSET']
01253         //      : false);
01254         // }
01255 
01256         if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01257             $postLimitTail .= ' FOR UPDATE';
01258         }
01259 
01260         if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01261             $postLimitTail .= ' LOCK IN SHARE MODE';
01262         }
01263 
01264         if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01265             $startOpts .= 'DISTINCT';
01266         }
01267 
01268         # Various MySQL extensions
01269         if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01270             $startOpts .= ' /*! STRAIGHT_JOIN */';
01271         }
01272 
01273         if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01274             $startOpts .= ' HIGH_PRIORITY';
01275         }
01276 
01277         if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01278             $startOpts .= ' SQL_BIG_RESULT';
01279         }
01280 
01281         if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01282             $startOpts .= ' SQL_BUFFER_RESULT';
01283         }
01284 
01285         if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01286             $startOpts .= ' SQL_SMALL_RESULT';
01287         }
01288 
01289         if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01290             $startOpts .= ' SQL_CALC_FOUND_ROWS';
01291         }
01292 
01293         if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01294             $startOpts .= ' SQL_CACHE';
01295         }
01296 
01297         if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01298             $startOpts .= ' SQL_NO_CACHE';
01299         }
01300 
01301         if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
01302             $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01303         } else {
01304             $useIndex = '';
01305         }
01306 
01307         return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01308     }
01309 
01318     public function makeGroupByWithHaving( $options ) {
01319         $sql = '';
01320         if ( isset( $options['GROUP BY'] ) ) {
01321             $gb = is_array( $options['GROUP BY'] )
01322                 ? implode( ',', $options['GROUP BY'] )
01323                 : $options['GROUP BY'];
01324             $sql .= ' GROUP BY ' . $gb;
01325         }
01326         if ( isset( $options['HAVING'] ) ) {
01327             $having = is_array( $options['HAVING'] )
01328                 ? $this->makeList( $options['HAVING'], LIST_AND )
01329                 : $options['HAVING'];
01330             $sql .= ' HAVING ' . $having;
01331         }
01332         return $sql;
01333     }
01334 
01343     public function makeOrderBy( $options ) {
01344         if ( isset( $options['ORDER BY'] ) ) {
01345             $ob = is_array( $options['ORDER BY'] )
01346                 ? implode( ',', $options['ORDER BY'] )
01347                 : $options['ORDER BY'];
01348             return ' ORDER BY ' . $ob;
01349         }
01350         return '';
01351     }
01352 
01492     public function select( $table, $vars, $conds = '', $fname = __METHOD__,
01493         $options = array(), $join_conds = array() ) {
01494         $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01495 
01496         return $this->query( $sql, $fname );
01497     }
01498 
01515     public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
01516         $options = array(), $join_conds = array() )
01517     {
01518         if ( is_array( $vars ) ) {
01519             $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
01520         }
01521 
01522         $options = (array)$options;
01523         $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01524             ? $options['USE INDEX']
01525             : array();
01526 
01527         if ( is_array( $table ) ) {
01528             $from = ' FROM ' .
01529                 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
01530         } elseif ( $table != '' ) {
01531             if ( $table[0] == ' ' ) {
01532                 $from = ' FROM ' . $table;
01533             } else {
01534                 $from = ' FROM ' .
01535                     $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() );
01536             }
01537         } else {
01538             $from = '';
01539         }
01540 
01541         list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
01542             $this->makeSelectOptions( $options );
01543 
01544         if ( !empty( $conds ) ) {
01545             if ( is_array( $conds ) ) {
01546                 $conds = $this->makeList( $conds, LIST_AND );
01547             }
01548             $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01549         } else {
01550             $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01551         }
01552 
01553         if ( isset( $options['LIMIT'] ) ) {
01554             $sql = $this->limitResult( $sql, $options['LIMIT'],
01555                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01556         }
01557         $sql = "$sql $postLimitTail";
01558 
01559         if ( isset( $options['EXPLAIN'] ) ) {
01560             $sql = 'EXPLAIN ' . $sql;
01561         }
01562 
01563         return $sql;
01564     }
01565 
01580     public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
01581         $options = array(), $join_conds = array() )
01582     {
01583         $options = (array)$options;
01584         $options['LIMIT'] = 1;
01585         $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01586 
01587         if ( $res === false ) {
01588             return false;
01589         }
01590 
01591         if ( !$this->numRows( $res ) ) {
01592             return false;
01593         }
01594 
01595         $obj = $this->fetchObject( $res );
01596 
01597         return $obj;
01598     }
01599 
01620     public function estimateRowCount( $table, $vars = '*', $conds = '',
01621         $fname = __METHOD__, $options = array() )
01622     {
01623         $rows = 0;
01624         $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
01625 
01626         if ( $res ) {
01627             $row = $this->fetchRow( $res );
01628             $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01629         }
01630 
01631         return $rows;
01632     }
01633 
01642     static function generalizeSQL( $sql ) {
01643         # This does the same as the regexp below would do, but in such a way
01644         # as to avoid crashing php on some large strings.
01645         # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
01646 
01647         $sql = str_replace( "\\\\", '', $sql );
01648         $sql = str_replace( "\\'", '', $sql );
01649         $sql = str_replace( "\\\"", '', $sql );
01650         $sql = preg_replace( "/'.*'/s", "'X'", $sql );
01651         $sql = preg_replace( '/".*"/s', "'X'", $sql );
01652 
01653         # All newlines, tabs, etc replaced by single space
01654         $sql = preg_replace( '/\s+/', ' ', $sql );
01655 
01656         # All numbers => N
01657         $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
01658         $sql = preg_replace( '/-?\d+/s', 'N', $sql );
01659 
01660         return $sql;
01661     }
01662 
01671     public function fieldExists( $table, $field, $fname = __METHOD__ ) {
01672         $info = $this->fieldInfo( $table, $field );
01673 
01674         return (bool)$info;
01675     }
01676 
01688     public function indexExists( $table, $index, $fname = __METHOD__ ) {
01689         if ( !$this->tableExists( $table ) ) {
01690             return null;
01691         }
01692 
01693         $info = $this->indexInfo( $table, $index, $fname );
01694         if ( is_null( $info ) ) {
01695             return null;
01696         } else {
01697             return $info !== false;
01698         }
01699     }
01700 
01709     public function tableExists( $table, $fname = __METHOD__ ) {
01710         $table = $this->tableName( $table );
01711         $old = $this->ignoreErrors( true );
01712         $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01713         $this->ignoreErrors( $old );
01714 
01715         return (bool)$res;
01716     }
01717 
01724     public function fieldType( $res, $index ) {
01725         if ( $res instanceof ResultWrapper ) {
01726             $res = $res->result;
01727         }
01728 
01729         return mysql_field_type( $res, $index );
01730     }
01731 
01740     public function indexUnique( $table, $index ) {
01741         $indexInfo = $this->indexInfo( $table, $index );
01742 
01743         if ( !$indexInfo ) {
01744             return null;
01745         }
01746 
01747         return !$indexInfo[0]->Non_unique;
01748     }
01749 
01756     protected function makeInsertOptions( $options ) {
01757         return implode( ' ', $options );
01758     }
01759 
01793     public function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
01794         # No rows to insert, easy just return now
01795         if ( !count( $a ) ) {
01796             return true;
01797         }
01798 
01799         $table = $this->tableName( $table );
01800 
01801         if ( !is_array( $options ) ) {
01802             $options = array( $options );
01803         }
01804 
01805         $fh = null;
01806         if ( isset( $options['fileHandle'] ) ) {
01807             $fh = $options['fileHandle'];
01808         }
01809         $options = $this->makeInsertOptions( $options );
01810 
01811         if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01812             $multi = true;
01813             $keys = array_keys( $a[0] );
01814         } else {
01815             $multi = false;
01816             $keys = array_keys( $a );
01817         }
01818 
01819         $sql = 'INSERT ' . $options .
01820             " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01821 
01822         if ( $multi ) {
01823             $first = true;
01824             foreach ( $a as $row ) {
01825                 if ( $first ) {
01826                     $first = false;
01827                 } else {
01828                     $sql .= ',';
01829                 }
01830                 $sql .= '(' . $this->makeList( $row ) . ')';
01831             }
01832         } else {
01833             $sql .= '(' . $this->makeList( $a ) . ')';
01834         }
01835 
01836         if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
01837             return false;
01838         } elseif ( $fh !== null ) {
01839             return true;
01840         }
01841 
01842         return (bool)$this->query( $sql, $fname );
01843     }
01844 
01851     protected function makeUpdateOptions( $options ) {
01852         if ( !is_array( $options ) ) {
01853             $options = array( $options );
01854         }
01855 
01856         $opts = array();
01857 
01858         if ( in_array( 'LOW_PRIORITY', $options ) ) {
01859             $opts[] = $this->lowPriorityOption();
01860         }
01861 
01862         if ( in_array( 'IGNORE', $options ) ) {
01863             $opts[] = 'IGNORE';
01864         }
01865 
01866         return implode( ' ', $opts );
01867     }
01868 
01892     function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
01893         $table = $this->tableName( $table );
01894         $opts = $this->makeUpdateOptions( $options );
01895         $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01896 
01897         if ( $conds !== array() && $conds !== '*' ) {
01898             $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01899         }
01900 
01901         return $this->query( $sql, $fname );
01902     }
01903 
01918     public function makeList( $a, $mode = LIST_COMMA ) {
01919         if ( !is_array( $a ) ) {
01920             throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
01921         }
01922 
01923         $first = true;
01924         $list = '';
01925 
01926         foreach ( $a as $field => $value ) {
01927             if ( !$first ) {
01928                 if ( $mode == LIST_AND ) {
01929                     $list .= ' AND ';
01930                 } elseif ( $mode == LIST_OR ) {
01931                     $list .= ' OR ';
01932                 } else {
01933                     $list .= ',';
01934                 }
01935             } else {
01936                 $first = false;
01937             }
01938 
01939             if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
01940                 $list .= "($value)";
01941             } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
01942                 $list .= "$value";
01943             } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
01944                 if ( count( $value ) == 0 ) {
01945                     throw new MWException( __METHOD__ . ": empty input for field $field" );
01946                 } elseif ( count( $value ) == 1 ) {
01947                     // Special-case single values, as IN isn't terribly efficient
01948                     // Don't necessarily assume the single key is 0; we don't
01949                     // enforce linear numeric ordering on other arrays here.
01950                     $value = array_values( $value );
01951                     $list .= $field . " = " . $this->addQuotes( $value[0] );
01952                 } else {
01953                     $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
01954                 }
01955             } elseif ( $value === null ) {
01956                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
01957                     $list .= "$field IS ";
01958                 } elseif ( $mode == LIST_SET ) {
01959                     $list .= "$field = ";
01960                 }
01961                 $list .= 'NULL';
01962             } else {
01963                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
01964                     $list .= "$field = ";
01965                 }
01966                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
01967             }
01968         }
01969 
01970         return $list;
01971     }
01972 
01983     public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
01984         $conds = array();
01985 
01986         foreach ( $data as $base => $sub ) {
01987             if ( count( $sub ) ) {
01988                 $conds[] = $this->makeList(
01989                     array( $baseKey => $base, $subKey => array_keys( $sub ) ),
01990                     LIST_AND );
01991             }
01992         }
01993 
01994         if ( $conds ) {
01995             return $this->makeList( $conds, LIST_OR );
01996         } else {
01997             // Nothing to search for...
01998             return false;
01999         }
02000     }
02001 
02010     public function aggregateValue( $valuedata, $valuename = 'value' ) {
02011         return $valuename;
02012     }
02013 
02018     public function bitNot( $field ) {
02019         return "(~$field)";
02020     }
02021 
02027     public function bitAnd( $fieldLeft, $fieldRight ) {
02028         return "($fieldLeft & $fieldRight)";
02029     }
02030 
02036     public function bitOr( $fieldLeft, $fieldRight ) {
02037         return "($fieldLeft | $fieldRight)";
02038     }
02039 
02045     public function buildConcat( $stringList ) {
02046         return 'CONCAT(' . implode( ',', $stringList ) . ')';
02047     }
02048 
02058     public function selectDB( $db ) {
02059         # Stub.  Shouldn't cause serious problems if it's not overridden, but
02060         # if your database engine supports a concept similar to MySQL's
02061         # databases you may as well.
02062         $this->mDBname = $db;
02063         return true;
02064     }
02065 
02069     public function getDBname() {
02070         return $this->mDBname;
02071     }
02072 
02076     public function getServer() {
02077         return $this->mServer;
02078     }
02079 
02097     public function tableName( $name, $format = 'quoted' ) {
02098         global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
02099         # Skip the entire process when we have a string quoted on both ends.
02100         # Note that we check the end so that we will still quote any use of
02101         # use of `database`.table. But won't break things if someone wants
02102         # to query a database table with a dot in the name.
02103         if ( $this->isQuotedIdentifier( $name ) ) {
02104             return $name;
02105         }
02106 
02107         # Lets test for any bits of text that should never show up in a table
02108         # name. Basically anything like JOIN or ON which are actually part of
02109         # SQL queries, but may end up inside of the table value to combine
02110         # sql. Such as how the API is doing.
02111         # Note that we use a whitespace test rather than a \b test to avoid
02112         # any remote case where a word like on may be inside of a table name
02113         # surrounded by symbols which may be considered word breaks.
02114         if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
02115             return $name;
02116         }
02117 
02118         # Split database and table into proper variables.
02119         # We reverse the explode so that database.table and table both output
02120         # the correct table.
02121         $dbDetails = explode( '.', $name, 2 );
02122         if ( count( $dbDetails ) == 2 ) {
02123             list( $database, $table ) = $dbDetails;
02124             # We don't want any prefix added in this case
02125             $prefix = '';
02126         } else {
02127             list( $table ) = $dbDetails;
02128             if ( $wgSharedDB !== null # We have a shared database
02129                 && $this->mForeign == false # We're not working on a foreign database
02130                 && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
02131                 && in_array( $table, $wgSharedTables ) # A shared table is selected
02132             ) {
02133                 $database = $wgSharedDB;
02134                 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
02135             } else {
02136                 $database = null;
02137                 $prefix = $this->mTablePrefix; # Default prefix
02138             }
02139         }
02140 
02141         # Quote $table and apply the prefix if not quoted.
02142         $tableName = "{$prefix}{$table}";
02143         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) {
02144             $tableName = $this->addIdentifierQuotes( $tableName );
02145         }
02146 
02147         # Quote $database and merge it with the table name if needed
02148         if ( $database !== null ) {
02149             if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
02150                 $database = $this->addIdentifierQuotes( $database );
02151             }
02152             $tableName = $database . '.' . $tableName;
02153         }
02154 
02155         return $tableName;
02156     }
02157 
02169     public function tableNames() {
02170         $inArray = func_get_args();
02171         $retVal = array();
02172 
02173         foreach ( $inArray as $name ) {
02174             $retVal[$name] = $this->tableName( $name );
02175         }
02176 
02177         return $retVal;
02178     }
02179 
02191     public function tableNamesN() {
02192         $inArray = func_get_args();
02193         $retVal = array();
02194 
02195         foreach ( $inArray as $name ) {
02196             $retVal[] = $this->tableName( $name );
02197         }
02198 
02199         return $retVal;
02200     }
02201 
02210     public function tableNameWithAlias( $name, $alias = false ) {
02211         if ( !$alias || $alias == $name ) {
02212             return $this->tableName( $name );
02213         } else {
02214             return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02215         }
02216     }
02217 
02224     public function tableNamesWithAlias( $tables ) {
02225         $retval = array();
02226         foreach ( $tables as $alias => $table ) {
02227             if ( is_numeric( $alias ) ) {
02228                 $alias = $table;
02229             }
02230             $retval[] = $this->tableNameWithAlias( $table, $alias );
02231         }
02232         return $retval;
02233     }
02234 
02243     public function fieldNameWithAlias( $name, $alias = false ) {
02244         if ( !$alias || (string)$alias === (string)$name ) {
02245             return $name;
02246         } else {
02247             return $name . ' AS ' . $alias; //PostgreSQL needs AS
02248         }
02249     }
02250 
02257     public function fieldNamesWithAlias( $fields ) {
02258         $retval = array();
02259         foreach ( $fields as $alias => $field ) {
02260             if ( is_numeric( $alias ) ) {
02261                 $alias = $field;
02262             }
02263             $retval[] = $this->fieldNameWithAlias( $field, $alias );
02264         }
02265         return $retval;
02266     }
02267 
02277     protected function tableNamesWithUseIndexOrJOIN(
02278         $tables, $use_index = array(), $join_conds = array()
02279     ) {
02280         $ret = array();
02281         $retJOIN = array();
02282         $use_index = (array)$use_index;
02283         $join_conds = (array)$join_conds;
02284 
02285         foreach ( $tables as $alias => $table ) {
02286             if ( !is_string( $alias ) ) {
02287                 // No alias? Set it equal to the table name
02288                 $alias = $table;
02289             }
02290             // Is there a JOIN clause for this table?
02291             if ( isset( $join_conds[$alias] ) ) {
02292                 list( $joinType, $conds ) = $join_conds[$alias];
02293                 $tableClause = $joinType;
02294                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02295                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02296                     $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02297                     if ( $use != '' ) {
02298                         $tableClause .= ' ' . $use;
02299                     }
02300                 }
02301                 $on = $this->makeList( (array)$conds, LIST_AND );
02302                 if ( $on != '' ) {
02303                     $tableClause .= ' ON (' . $on . ')';
02304                 }
02305 
02306                 $retJOIN[] = $tableClause;
02307             // Is there an INDEX clause for this table?
02308             } elseif ( isset( $use_index[$alias] ) ) {
02309                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02310                 $tableClause .= ' ' . $this->useIndexClause(
02311                     implode( ',', (array)$use_index[$alias] ) );
02312 
02313                 $ret[] = $tableClause;
02314             } else {
02315                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02316 
02317                 $ret[] = $tableClause;
02318             }
02319         }
02320 
02321         // We can't separate explicit JOIN clauses with ',', use ' ' for those
02322         $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02323         $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02324 
02325         // Compile our final table clause
02326         return implode( ' ', array( $implicitJoins, $explicitJoins ) );
02327     }
02328 
02336     protected function indexName( $index ) {
02337         // Backwards-compatibility hack
02338         $renamed = array(
02339             'ar_usertext_timestamp' => 'usertext_timestamp',
02340             'un_user_id' => 'user_id',
02341             'un_user_ip' => 'user_ip',
02342         );
02343 
02344         if ( isset( $renamed[$index] ) ) {
02345             return $renamed[$index];
02346         } else {
02347             return $index;
02348         }
02349     }
02350 
02358     public function addQuotes( $s ) {
02359         if ( $s === null ) {
02360             return 'NULL';
02361         } else {
02362             # This will also quote numeric values. This should be harmless,
02363             # and protects against weird problems that occur when they really
02364             # _are_ strings such as article titles and string->number->string
02365             # conversion is not 1:1.
02366             return "'" . $this->strencode( $s ) . "'";
02367         }
02368     }
02369 
02380     public function addIdentifierQuotes( $s ) {
02381         return '"' . str_replace( '"', '""', $s ) . '"';
02382     }
02383 
02392     public function isQuotedIdentifier( $name ) {
02393         return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02394     }
02395 
02400     protected function escapeLikeInternal( $s ) {
02401         $s = str_replace( '\\', '\\\\', $s );
02402         $s = $this->strencode( $s );
02403         $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
02404 
02405         return $s;
02406     }
02407 
02420     public function buildLike() {
02421         $params = func_get_args();
02422 
02423         if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02424             $params = $params[0];
02425         }
02426 
02427         $s = '';
02428 
02429         foreach ( $params as $value ) {
02430             if ( $value instanceof LikeMatch ) {
02431                 $s .= $value->toString();
02432             } else {
02433                 $s .= $this->escapeLikeInternal( $value );
02434             }
02435         }
02436 
02437         return " LIKE '" . $s . "' ";
02438     }
02439 
02445     public function anyChar() {
02446         return new LikeMatch( '_' );
02447     }
02448 
02454     public function anyString() {
02455         return new LikeMatch( '%' );
02456     }
02457 
02469     public function nextSequenceValue( $seqName ) {
02470         return null;
02471     }
02472 
02483     public function useIndexClause( $index ) {
02484         return '';
02485     }
02486 
02509     public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
02510         $quotedTable = $this->tableName( $table );
02511 
02512         if ( count( $rows ) == 0 ) {
02513             return;
02514         }
02515 
02516         # Single row case
02517         if ( !is_array( reset( $rows ) ) ) {
02518             $rows = array( $rows );
02519         }
02520 
02521         foreach ( $rows as $row ) {
02522             # Delete rows which collide
02523             if ( $uniqueIndexes ) {
02524                 $sql = "DELETE FROM $quotedTable WHERE ";
02525                 $first = true;
02526                 foreach ( $uniqueIndexes as $index ) {
02527                     if ( $first ) {
02528                         $first = false;
02529                         $sql .= '( ';
02530                     } else {
02531                         $sql .= ' ) OR ( ';
02532                     }
02533                     if ( is_array( $index ) ) {
02534                         $first2 = true;
02535                         foreach ( $index as $col ) {
02536                             if ( $first2 ) {
02537                                 $first2 = false;
02538                             } else {
02539                                 $sql .= ' AND ';
02540                             }
02541                             $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02542                         }
02543                     } else {
02544                         $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02545                     }
02546                 }
02547                 $sql .= ' )';
02548                 $this->query( $sql, $fname );
02549             }
02550 
02551             # Now insert the row
02552             $this->insert( $table, $row, $fname );
02553         }
02554     }
02555 
02566     protected function nativeReplace( $table, $rows, $fname ) {
02567         $table = $this->tableName( $table );
02568 
02569         # Single row case
02570         if ( !is_array( reset( $rows ) ) ) {
02571             $rows = array( $rows );
02572         }
02573 
02574         $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02575         $first = true;
02576 
02577         foreach ( $rows as $row ) {
02578             if ( $first ) {
02579                 $first = false;
02580             } else {
02581                 $sql .= ',';
02582             }
02583 
02584             $sql .= '(' . $this->makeList( $row ) . ')';
02585         }
02586 
02587         return $this->query( $sql, $fname );
02588     }
02589 
02625     public function upsert(
02626         $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
02627     ) {
02628         if ( !count( $rows ) ) {
02629             return true; // nothing to do
02630         }
02631         $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
02632 
02633         if ( count( $uniqueIndexes ) ) {
02634             $clauses = array(); // list WHERE clauses that each identify a single row
02635             foreach ( $rows as $row ) {
02636                 foreach ( $uniqueIndexes as $index ) {
02637                     $index = is_array( $index ) ? $index : array( $index ); // columns
02638                     $rowKey = array(); // unique key to this row
02639                     foreach ( $index as $column ) {
02640                         $rowKey[$column] = $row[$column];
02641                     }
02642                     $clauses[] = $this->makeList( $rowKey, LIST_AND );
02643                 }
02644             }
02645             $where = array( $this->makeList( $clauses, LIST_OR ) );
02646         } else {
02647             $where = false;
02648         }
02649 
02650         $useTrx = !$this->mTrxLevel;
02651         if ( $useTrx ) {
02652             $this->begin( $fname );
02653         }
02654         try {
02655             # Update any existing conflicting row(s)
02656             if ( $where !== false ) {
02657                 $ok = $this->update( $table, $set, $where, $fname );
02658             } else {
02659                 $ok = true;
02660             }
02661             # Now insert any non-conflicting row(s)
02662             $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok;
02663         } catch ( Exception $e ) {
02664             if ( $useTrx ) {
02665                 $this->rollback( $fname );
02666             }
02667             throw $e;
02668         }
02669         if ( $useTrx ) {
02670             $this->commit( $fname );
02671         }
02672 
02673         return $ok;
02674     }
02675 
02697     public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02698         $fname = __METHOD__ )
02699     {
02700         if ( !$conds ) {
02701             throw new DBUnexpectedError( $this,
02702                 'DatabaseBase::deleteJoin() called with empty $conds' );
02703         }
02704 
02705         $delTable = $this->tableName( $delTable );
02706         $joinTable = $this->tableName( $joinTable );
02707         $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02708         if ( $conds != '*' ) {
02709             $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02710         }
02711         $sql .= ')';
02712 
02713         $this->query( $sql, $fname );
02714     }
02715 
02724     public function textFieldSize( $table, $field ) {
02725         $table = $this->tableName( $table );
02726         $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02727         $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02728         $row = $this->fetchObject( $res );
02729 
02730         $m = array();
02731 
02732         if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02733             $size = $m[1];
02734         } else {
02735             $size = -1;
02736         }
02737 
02738         return $size;
02739     }
02740 
02749     public function lowPriorityOption() {
02750         return '';
02751     }
02752 
02764     public function delete( $table, $conds, $fname = __METHOD__ ) {
02765         if ( !$conds ) {
02766             throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02767         }
02768 
02769         $table = $this->tableName( $table );
02770         $sql = "DELETE FROM $table";
02771 
02772         if ( $conds != '*' ) {
02773             if ( is_array( $conds ) ) {
02774                 $conds = $this->makeList( $conds, LIST_AND );
02775             }
02776             $sql .= ' WHERE ' . $conds;
02777         }
02778 
02779         return $this->query( $sql, $fname );
02780     }
02781 
02808     public function insertSelect( $destTable, $srcTable, $varMap, $conds,
02809         $fname = __METHOD__,
02810         $insertOptions = array(), $selectOptions = array() )
02811     {
02812         $destTable = $this->tableName( $destTable );
02813 
02814         if ( is_array( $insertOptions ) ) {
02815             $insertOptions = implode( ' ', $insertOptions );
02816         }
02817 
02818         if ( !is_array( $selectOptions ) ) {
02819             $selectOptions = array( $selectOptions );
02820         }
02821 
02822         list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
02823 
02824         if ( is_array( $srcTable ) ) {
02825             $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
02826         } else {
02827             $srcTable = $this->tableName( $srcTable );
02828         }
02829 
02830         $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
02831             " SELECT $startOpts " . implode( ',', $varMap ) .
02832             " FROM $srcTable $useIndex ";
02833 
02834         if ( $conds != '*' ) {
02835             if ( is_array( $conds ) ) {
02836                 $conds = $this->makeList( $conds, LIST_AND );
02837             }
02838             $sql .= " WHERE $conds";
02839         }
02840 
02841         $sql .= " $tailOpts";
02842 
02843         return $this->query( $sql, $fname );
02844     }
02845 
02866     public function limitResult( $sql, $limit, $offset = false ) {
02867         if ( !is_numeric( $limit ) ) {
02868             throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
02869         }
02870         return "$sql LIMIT "
02871             . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
02872             . "{$limit} ";
02873     }
02874 
02880     public function unionSupportsOrderAndLimit() {
02881         return true; // True for almost every DB supported
02882     }
02883 
02892     public function unionQueries( $sqls, $all ) {
02893         $glue = $all ? ') UNION ALL (' : ') UNION (';
02894         return '(' . implode( $glue, $sqls ) . ')';
02895     }
02896 
02906     public function conditional( $cond, $trueVal, $falseVal ) {
02907         if ( is_array( $cond ) ) {
02908             $cond = $this->makeList( $cond, LIST_AND );
02909         }
02910         return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
02911     }
02912 
02923     public function strreplace( $orig, $old, $new ) {
02924         return "REPLACE({$orig}, {$old}, {$new})";
02925     }
02926 
02933     public function getServerUptime() {
02934         return 0;
02935     }
02936 
02943     public function wasDeadlock() {
02944         return false;
02945     }
02946 
02953     public function wasLockTimeout() {
02954         return false;
02955     }
02956 
02964     public function wasErrorReissuable() {
02965         return false;
02966     }
02967 
02974     public function wasReadOnlyError() {
02975         return false;
02976     }
02977 
02996     public function deadlockLoop() {
02997         $this->begin( __METHOD__ );
02998         $args = func_get_args();
02999         $function = array_shift( $args );
03000         $oldIgnore = $this->ignoreErrors( true );
03001         $tries = self::DEADLOCK_TRIES;
03002 
03003         if ( is_array( $function ) ) {
03004             $fname = $function[0];
03005         } else {
03006             $fname = $function;
03007         }
03008 
03009         do {
03010             $retVal = call_user_func_array( $function, $args );
03011             $error = $this->lastError();
03012             $errno = $this->lastErrno();
03013             $sql = $this->lastQuery();
03014 
03015             if ( $errno ) {
03016                 if ( $this->wasDeadlock() ) {
03017                     # Retry
03018                     usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
03019                 } else {
03020                     $this->reportQueryError( $error, $errno, $sql, $fname );
03021                 }
03022             }
03023         } while ( $this->wasDeadlock() && --$tries > 0 );
03024 
03025         $this->ignoreErrors( $oldIgnore );
03026 
03027         if ( $tries <= 0 ) {
03028             $this->rollback( __METHOD__ );
03029             $this->reportQueryError( $error, $errno, $sql, $fname );
03030             return false;
03031         } else {
03032             $this->commit( __METHOD__ );
03033             return $retVal;
03034         }
03035     }
03036 
03048     public function masterPosWait( DBMasterPos $pos, $timeout ) {
03049         wfProfileIn( __METHOD__ );
03050 
03051         if ( !is_null( $this->mFakeSlaveLag ) ) {
03052             $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
03053 
03054             if ( $wait > $timeout * 1e6 ) {
03055                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
03056                 wfProfileOut( __METHOD__ );
03057                 return -1;
03058             } elseif ( $wait > 0 ) {
03059                 wfDebug( "Fake slave waiting $wait us\n" );
03060                 usleep( $wait );
03061                 wfProfileOut( __METHOD__ );
03062                 return 1;
03063             } else {
03064                 wfDebug( "Fake slave up to date ($wait us)\n" );
03065                 wfProfileOut( __METHOD__ );
03066                 return 0;
03067             }
03068         }
03069 
03070         wfProfileOut( __METHOD__ );
03071 
03072         # Real waits are implemented in the subclass.
03073         return 0;
03074     }
03075 
03081     public function getSlavePos() {
03082         if ( !is_null( $this->mFakeSlaveLag ) ) {
03083             $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
03084             wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
03085             return $pos;
03086         } else {
03087             # Stub
03088             return false;
03089         }
03090     }
03091 
03097     public function getMasterPos() {
03098         if ( $this->mFakeMaster ) {
03099             return new MySQLMasterPos( 'fake', microtime( true ) );
03100         } else {
03101             return false;
03102         }
03103     }
03104 
03119     final public function onTransactionIdle( $callback ) {
03120         $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() );
03121         if ( !$this->mTrxLevel ) {
03122             $this->runOnTransactionIdleCallbacks();
03123         }
03124     }
03125 
03137     final public function onTransactionPreCommitOrIdle( $callback ) {
03138         if ( $this->mTrxLevel ) {
03139             $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() );
03140         } else {
03141             $this->onTransactionIdle( $callback ); // this will trigger immediately
03142         }
03143     }
03144 
03150     protected function runOnTransactionIdleCallbacks() {
03151         $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
03152 
03153         $e = null; // last exception
03154         do { // callbacks may add callbacks :)
03155             $callbacks = $this->mTrxIdleCallbacks;
03156             $this->mTrxIdleCallbacks = array(); // recursion guard
03157             foreach ( $callbacks as $callback ) {
03158                 try {
03159                     list( $phpCallback ) = $callback;
03160                     $this->clearFlag( DBO_TRX ); // make each query its own transaction
03161                     call_user_func( $phpCallback );
03162                     $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
03163                 } catch ( Exception $e ) {}
03164             }
03165         } while ( count( $this->mTrxIdleCallbacks ) );
03166 
03167         if ( $e instanceof Exception ) {
03168             throw $e; // re-throw any last exception
03169         }
03170     }
03171 
03177     protected function runOnTransactionPreCommitCallbacks() {
03178         $e = null; // last exception
03179         do { // callbacks may add callbacks :)
03180             $callbacks = $this->mTrxPreCommitCallbacks;
03181             $this->mTrxPreCommitCallbacks = array(); // recursion guard
03182             foreach ( $callbacks as $callback ) {
03183                 try {
03184                     list( $phpCallback ) = $callback;
03185                     call_user_func( $phpCallback );
03186                 } catch ( Exception $e ) {}
03187             }
03188         } while ( count( $this->mTrxPreCommitCallbacks ) );
03189 
03190         if ( $e instanceof Exception ) {
03191             throw $e; // re-throw any last exception
03192         }
03193     }
03194 
03207     final public function begin( $fname = __METHOD__ ) {
03208         global $wgDebugDBTransactions;
03209 
03210         if ( $this->mTrxLevel ) { // implicit commit
03211             if ( !$this->mTrxAutomatic ) {
03212                 // We want to warn about inadvertently nested begin/commit pairs, but not about
03213                 // auto-committing implicit transactions that were started by query() via DBO_TRX
03214                 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
03215                     " performing implicit commit!";
03216                 wfWarn( $msg );
03217                 wfLogDBError( $msg );
03218             } else {
03219                 // if the transaction was automatic and has done write operations,
03220                 // log it if $wgDebugDBTransactions is enabled.
03221                 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
03222                     wfDebug( "$fname: Automatic transaction with writes in progress" .
03223                         " (from {$this->mTrxFname}), performing implicit commit!\n"
03224                     );
03225                 }
03226             }
03227 
03228             $this->runOnTransactionPreCommitCallbacks();
03229             $this->doCommit( $fname );
03230             if ( $this->mTrxDoneWrites ) {
03231                 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
03232             }
03233             $this->runOnTransactionIdleCallbacks();
03234         }
03235 
03236         $this->doBegin( $fname );
03237         $this->mTrxFname = $fname;
03238         $this->mTrxDoneWrites = false;
03239         $this->mTrxAutomatic = false;
03240     }
03241 
03248     protected function doBegin( $fname ) {
03249         $this->query( 'BEGIN', $fname );
03250         $this->mTrxLevel = 1;
03251     }
03252 
03265     final public function commit( $fname = __METHOD__, $flush = '' ) {
03266         if ( $flush != 'flush' ) {
03267             if ( !$this->mTrxLevel ) {
03268                 wfWarn( "$fname: No transaction to commit, something got out of sync!" );
03269             } elseif ( $this->mTrxAutomatic ) {
03270                 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
03271             }
03272         } else {
03273             if ( !$this->mTrxLevel ) {
03274                 return; // nothing to do
03275             } elseif ( !$this->mTrxAutomatic ) {
03276                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03277             }
03278         }
03279 
03280         $this->runOnTransactionPreCommitCallbacks();
03281         $this->doCommit( $fname );
03282         if ( $this->mTrxDoneWrites ) {
03283             Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
03284         }
03285         $this->mTrxDoneWrites = false;
03286         $this->runOnTransactionIdleCallbacks();
03287     }
03288 
03295     protected function doCommit( $fname ) {
03296         if ( $this->mTrxLevel ) {
03297             $this->query( 'COMMIT', $fname );
03298             $this->mTrxLevel = 0;
03299         }
03300     }
03301 
03310     final public function rollback( $fname = __METHOD__ ) {
03311         if ( !$this->mTrxLevel ) {
03312             wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
03313         }
03314         $this->doRollback( $fname );
03315         $this->mTrxIdleCallbacks = array(); // cancel
03316         $this->mTrxPreCommitCallbacks = array(); // cancel
03317         if ( $this->mTrxDoneWrites ) {
03318             Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
03319         }
03320         $this->mTrxDoneWrites = false;
03321     }
03322 
03329     protected function doRollback( $fname ) {
03330         if ( $this->mTrxLevel ) {
03331             $this->query( 'ROLLBACK', $fname, true );
03332             $this->mTrxLevel = 0;
03333         }
03334     }
03335 
03351     public function duplicateTableStructure( $oldName, $newName, $temporary = false,
03352         $fname = __METHOD__
03353     ) {
03354         throw new MWException(
03355             'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
03356     }
03357 
03365     function listTables( $prefix = null, $fname = __METHOD__ ) {
03366         throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
03367     }
03368 
03373     final public function clearViewsCache() {
03374         $this->allViews = null;
03375     }
03376 
03388     public function listViews( $prefix = null, $fname = __METHOD__ ) {
03389         throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
03390     }
03391 
03399     public function isView( $name ) {
03400         throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
03401     }
03402 
03414     public function timestamp( $ts = 0 ) {
03415         return wfTimestamp( TS_MW, $ts );
03416     }
03417 
03431     public function timestampOrNull( $ts = null ) {
03432         if ( is_null( $ts ) ) {
03433             return null;
03434         } else {
03435             return $this->timestamp( $ts );
03436         }
03437     }
03438 
03454     public function resultObject( $result ) {
03455         if ( empty( $result ) ) {
03456             return false;
03457         } elseif ( $result instanceof ResultWrapper ) {
03458             return $result;
03459         } elseif ( $result === true ) {
03460             // Successful write query
03461             return $result;
03462         } else {
03463             return new ResultWrapper( $this, $result );
03464         }
03465     }
03466 
03472     public function ping() {
03473         # Stub.  Not essential to override.
03474         return true;
03475     }
03476 
03486     public function getLag() {
03487         return intval( $this->mFakeSlaveLag );
03488     }
03489 
03495     function maxListLen() {
03496         return 0;
03497     }
03498 
03507     public function encodeBlob( $b ) {
03508         return $b;
03509     }
03510 
03518     public function decodeBlob( $b ) {
03519         return $b;
03520     }
03521 
03532     public function setSessionOptions( array $options ) {
03533     }
03534 
03551     public function sourceFile(
03552         $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
03553     ) {
03554         wfSuppressWarnings();
03555         $fp = fopen( $filename, 'r' );
03556         wfRestoreWarnings();
03557 
03558         if ( false === $fp ) {
03559             throw new MWException( "Could not open \"{$filename}\".\n" );
03560         }
03561 
03562         if ( !$fname ) {
03563             $fname = __METHOD__ . "( $filename )";
03564         }
03565 
03566         try {
03567             $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
03568         }
03569         catch ( MWException $e ) {
03570             fclose( $fp );
03571             throw $e;
03572         }
03573 
03574         fclose( $fp );
03575 
03576         return $error;
03577     }
03578 
03587     public function patchPath( $patch ) {
03588         global $IP;
03589 
03590         $dbType = $this->getType();
03591         if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03592             return "$IP/maintenance/$dbType/archives/$patch";
03593         } else {
03594             return "$IP/maintenance/archives/$patch";
03595         }
03596     }
03597 
03605     public function setSchemaVars( $vars ) {
03606         $this->mSchemaVars = $vars;
03607     }
03608 
03622     public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03623         $fname = __METHOD__, $inputCallback = false )
03624     {
03625         $cmd = '';
03626 
03627         while ( !feof( $fp ) ) {
03628             if ( $lineCallback ) {
03629                 call_user_func( $lineCallback );
03630             }
03631 
03632             $line = trim( fgets( $fp ) );
03633 
03634             if ( $line == '' ) {
03635                 continue;
03636             }
03637 
03638             if ( '-' == $line[0] && '-' == $line[1] ) {
03639                 continue;
03640             }
03641 
03642             if ( $cmd != '' ) {
03643                 $cmd .= ' ';
03644             }
03645 
03646             $done = $this->streamStatementEnd( $cmd, $line );
03647 
03648             $cmd .= "$line\n";
03649 
03650             if ( $done || feof( $fp ) ) {
03651                 $cmd = $this->replaceVars( $cmd );
03652 
03653                 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
03654                     $res = $this->query( $cmd, $fname );
03655 
03656                     if ( $resultCallback ) {
03657                         call_user_func( $resultCallback, $res, $this );
03658                     }
03659 
03660                     if ( false === $res ) {
03661                         $err = $this->lastError();
03662                         return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03663                     }
03664                 }
03665                 $cmd = '';
03666             }
03667         }
03668 
03669         return true;
03670     }
03671 
03679     public function streamStatementEnd( &$sql, &$newLine ) {
03680         if ( $this->delimiter ) {
03681             $prev = $newLine;
03682             $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
03683             if ( $newLine != $prev ) {
03684                 return true;
03685             }
03686         }
03687         return false;
03688     }
03689 
03707     protected function replaceSchemaVars( $ins ) {
03708         $vars = $this->getSchemaVars();
03709         foreach ( $vars as $var => $value ) {
03710             // replace '{$var}'
03711             $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
03712             // replace `{$var}`
03713             $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
03714             // replace /*$var*/
03715             $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
03716         }
03717         return $ins;
03718     }
03719 
03727     protected function replaceVars( $ins ) {
03728         $ins = $this->replaceSchemaVars( $ins );
03729 
03730         // Table prefixes
03731         $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
03732             array( $this, 'tableNameCallback' ), $ins );
03733 
03734         // Index names
03735         $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
03736             array( $this, 'indexNameCallback' ), $ins );
03737 
03738         return $ins;
03739     }
03740 
03747     protected function getSchemaVars() {
03748         if ( $this->mSchemaVars ) {
03749             return $this->mSchemaVars;
03750         } else {
03751             return $this->getDefaultSchemaVars();
03752         }
03753     }
03754 
03763     protected function getDefaultSchemaVars() {
03764         return array();
03765     }
03766 
03774     protected function tableNameCallback( $matches ) {
03775         return $this->tableName( $matches[1] );
03776     }
03777 
03785     protected function indexNameCallback( $matches ) {
03786         return $this->indexName( $matches[1] );
03787     }
03788 
03797     public function lockIsFree( $lockName, $method ) {
03798         return true;
03799     }
03800 
03812     public function lock( $lockName, $method, $timeout = 5 ) {
03813         return true;
03814     }
03815 
03826     public function unlock( $lockName, $method ) {
03827         return true;
03828     }
03829 
03840     public function lockTables( $read, $write, $method, $lowPriority = true ) {
03841         return true;
03842     }
03843 
03851     public function unlockTables( $method ) {
03852         return true;
03853     }
03854 
03862     public function dropTable( $tableName, $fName = __METHOD__ ) {
03863         if ( !$this->tableExists( $tableName, $fName ) ) {
03864             return false;
03865         }
03866         $sql = "DROP TABLE " . $this->tableName( $tableName );
03867         if ( $this->cascadingDeletes() ) {
03868             $sql .= " CASCADE";
03869         }
03870         return $this->query( $sql, $fName );
03871     }
03872 
03879     public function getSearchEngine() {
03880         return 'SearchEngineDummy';
03881     }
03882 
03890     public function getInfinity() {
03891         return 'infinity';
03892     }
03893 
03900     public function encodeExpiry( $expiry ) {
03901         return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
03902             ? $this->getInfinity()
03903             : $this->timestamp( $expiry );
03904     }
03905 
03913     public function decodeExpiry( $expiry, $format = TS_MW ) {
03914         return ( $expiry == '' || $expiry == $this->getInfinity() )
03915             ? 'infinity'
03916             : wfTimestamp( $format, $expiry );
03917     }
03918 
03928     public function setBigSelects( $value = true ) {
03929         // no-op
03930     }
03931 
03935     public function __toString() {
03936         return (string)$this->mConn;
03937     }
03938 
03942     public function __destruct() {
03943         if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
03944             trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
03945         }
03946         if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
03947             $callers = array();
03948             foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
03949                 $callers[] = $callbackInfo[1];
03950 
03951             }
03952             $callers = implode( ', ', $callers );
03953             trigger_error( "DB transaction  callbacks still pending (from $callers)." );
03954         }
03955     }
03956 }