MediaWiki  REL1_24
Database.php
Go to the documentation of this file.
00001 <?php
00002 
00035 interface DatabaseType {
00041     function getType();
00042 
00053     function open( $server, $user, $password, $dbName );
00054 
00065     function fetchObject( $res );
00066 
00076     function fetchRow( $res );
00077 
00084     function numRows( $res );
00085 
00093     function numFields( $res );
00094 
00103     function fieldName( $res, $n );
00104 
00117     function insertId();
00118 
00126     function dataSeek( $res, $row );
00127 
00134     function lastErrno();
00135 
00142     function lastError();
00143 
00153     function fieldInfo( $table, $field );
00154 
00162     function indexInfo( $table, $index, $fname = __METHOD__ );
00163 
00170     function affectedRows();
00171 
00178     function strencode( $s );
00179 
00188     function getSoftwareLink();
00189 
00196     function getServerVersion();
00197 
00205     function getServerInfo();
00206 }
00207 
00212 interface IDatabase {
00213 }
00214 
00219 abstract class DatabaseBase implements IDatabase, DatabaseType {
00221     const DEADLOCK_TRIES = 4;
00222 
00224     const DEADLOCK_DELAY_MIN = 500000;
00225 
00227     const DEADLOCK_DELAY_MAX = 1500000;
00228 
00229 # ------------------------------------------------------------------------------
00230 # Variables
00231 # ------------------------------------------------------------------------------
00232 
00233     protected $mLastQuery = '';
00234     protected $mDoneWrites = false;
00235     protected $mPHPError = false;
00236 
00237     protected $mServer, $mUser, $mPassword, $mDBname;
00238 
00240     protected $mConn = null;
00241     protected $mOpened = false;
00242 
00244     protected $mTrxIdleCallbacks = array();
00246     protected $mTrxPreCommitCallbacks = array();
00247 
00248     protected $mTablePrefix;
00249     protected $mSchema;
00250     protected $mFlags;
00251     protected $mForeign;
00252     protected $mErrorCount = 0;
00253     protected $mLBInfo = array();
00254     protected $mDefaultBigSelects = null;
00255     protected $mSchemaVars = false;
00256 
00257     protected $preparedArgs;
00258 
00259     protected $htmlErrors;
00260 
00261     protected $delimiter = ';';
00262 
00269     protected $mTrxLevel = 0;
00270 
00276     protected $mTrxShortId = '';
00277 
00285     private $mTrxFname = null;
00286 
00293     private $mTrxDoneWrites = false;
00294 
00301     private $mTrxAutomatic = false;
00302 
00308     private $mTrxAtomicLevels;
00309 
00315     private $mTrxAutomaticAtomic = false;
00316 
00321     protected $fileHandle = null;
00322 
00327     protected $allViews = null;
00328 
00329 # ------------------------------------------------------------------------------
00330 # Accessors
00331 # ------------------------------------------------------------------------------
00332     # These optionally set a variable and return the previous state
00333 
00341     public function getServerInfo() {
00342         return $this->getServerVersion();
00343     }
00344 
00348     public function getDelimiter() {
00349         return $this->delimiter;
00350     }
00351 
00361     public function debug( $debug = null ) {
00362         return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00363     }
00364 
00386     public function bufferResults( $buffer = null ) {
00387         if ( is_null( $buffer ) ) {
00388             return !(bool)( $this->mFlags & DBO_NOBUFFER );
00389         } else {
00390             return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00391         }
00392     }
00393 
00406     public function ignoreErrors( $ignoreErrors = null ) {
00407         return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00408     }
00409 
00418     public function trxLevel() {
00419         return $this->mTrxLevel;
00420     }
00421 
00427     public function errorCount( $count = null ) {
00428         return wfSetVar( $this->mErrorCount, $count );
00429     }
00430 
00436     public function tablePrefix( $prefix = null ) {
00437         return wfSetVar( $this->mTablePrefix, $prefix );
00438     }
00439 
00445     public function dbSchema( $schema = null ) {
00446         return wfSetVar( $this->mSchema, $schema );
00447     }
00448 
00454     public function setFileHandle( $fh ) {
00455         $this->fileHandle = $fh;
00456     }
00457 
00467     public function getLBInfo( $name = null ) {
00468         if ( is_null( $name ) ) {
00469             return $this->mLBInfo;
00470         } else {
00471             if ( array_key_exists( $name, $this->mLBInfo ) ) {
00472                 return $this->mLBInfo[$name];
00473             } else {
00474                 return null;
00475             }
00476         }
00477     }
00478 
00487     public function setLBInfo( $name, $value = null ) {
00488         if ( is_null( $value ) ) {
00489             $this->mLBInfo = $name;
00490         } else {
00491             $this->mLBInfo[$name] = $value;
00492         }
00493     }
00494 
00502     public function setFakeSlaveLag( $lag ) {
00503     }
00504 
00510     public function setFakeMaster( $enabled = true ) {
00511     }
00512 
00518     public function cascadingDeletes() {
00519         return false;
00520     }
00521 
00527     public function cleanupTriggers() {
00528         return false;
00529     }
00530 
00537     public function strictIPs() {
00538         return false;
00539     }
00540 
00546     public function realTimestamps() {
00547         return false;
00548     }
00549 
00555     public function implicitGroupby() {
00556         return true;
00557     }
00558 
00565     public function implicitOrderby() {
00566         return true;
00567     }
00568 
00575     public function searchableIPs() {
00576         return false;
00577     }
00578 
00584     public function functionalIndexes() {
00585         return false;
00586     }
00587 
00592     public function lastQuery() {
00593         return $this->mLastQuery;
00594     }
00595 
00602     public function doneWrites() {
00603         return (bool)$this->mDoneWrites;
00604     }
00605 
00613     public function lastDoneWrites() {
00614         return $this->mDoneWrites ?: false;
00615     }
00616 
00623     public function writesOrCallbacksPending() {
00624         return $this->mTrxLevel && (
00625             $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
00626         );
00627     }
00628 
00633     public function isOpen() {
00634         return $this->mOpened;
00635     }
00636 
00648     public function setFlag( $flag ) {
00649         global $wgDebugDBTransactions;
00650         $this->mFlags |= $flag;
00651         if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00652             wfDebug( "Implicit transactions are now enabled.\n" );
00653         }
00654     }
00655 
00667     public function clearFlag( $flag ) {
00668         global $wgDebugDBTransactions;
00669         $this->mFlags &= ~$flag;
00670         if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00671             wfDebug( "Implicit transactions are now disabled.\n" );
00672         }
00673     }
00674 
00685     public function getFlag( $flag ) {
00686         return !!( $this->mFlags & $flag );
00687     }
00688 
00695     public function getProperty( $name ) {
00696         return $this->$name;
00697     }
00698 
00702     public function getWikiID() {
00703         if ( $this->mTablePrefix ) {
00704             return "{$this->mDBname}-{$this->mTablePrefix}";
00705         } else {
00706             return $this->mDBname;
00707         }
00708     }
00709 
00717     private function getSqlFilePath( $filename ) {
00718         global $IP;
00719         $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename";
00720         if ( file_exists( $dbmsSpecificFilePath ) ) {
00721             return $dbmsSpecificFilePath;
00722         } else {
00723             return "$IP/maintenance/$filename";
00724         }
00725     }
00726 
00733     public function getSchemaPath() {
00734         return $this->getSqlFilePath( 'tables.sql' );
00735     }
00736 
00743     public function getUpdateKeysPath() {
00744         return $this->getSqlFilePath( 'update-keys.sql' );
00745     }
00746 
00747 # ------------------------------------------------------------------------------
00748 # Other functions
00749 # ------------------------------------------------------------------------------
00750 
00763     function __construct( $params = null ) {
00764         global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
00765 
00766         $this->mTrxAtomicLevels = new SplStack;
00767 
00768         if ( is_array( $params ) ) { // MW 1.22
00769             $server = $params['host'];
00770             $user = $params['user'];
00771             $password = $params['password'];
00772             $dbName = $params['dbname'];
00773             $flags = $params['flags'];
00774             $tablePrefix = $params['tablePrefix'];
00775             $schema = $params['schema'];
00776             $foreign = $params['foreign'];
00777         } else { // legacy calling pattern
00778             wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" );
00779             $args = func_get_args();
00780             $server = isset( $args[0] ) ? $args[0] : false;
00781             $user = isset( $args[1] ) ? $args[1] : false;
00782             $password = isset( $args[2] ) ? $args[2] : false;
00783             $dbName = isset( $args[3] ) ? $args[3] : false;
00784             $flags = isset( $args[4] ) ? $args[4] : 0;
00785             $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global';
00786             $schema = 'get from global';
00787             $foreign = isset( $args[6] ) ? $args[6] : false;
00788         }
00789 
00790         $this->mFlags = $flags;
00791         if ( $this->mFlags & DBO_DEFAULT ) {
00792             if ( $wgCommandLineMode ) {
00793                 $this->mFlags &= ~DBO_TRX;
00794                 if ( $wgDebugDBTransactions ) {
00795                     wfDebug( "Implicit transaction open disabled.\n" );
00796                 }
00797             } else {
00798                 $this->mFlags |= DBO_TRX;
00799                 if ( $wgDebugDBTransactions ) {
00800                     wfDebug( "Implicit transaction open enabled.\n" );
00801                 }
00802             }
00803         }
00804 
00806         if ( $tablePrefix == 'get from global' ) {
00807             $this->mTablePrefix = $wgDBprefix;
00808         } else {
00809             $this->mTablePrefix = $tablePrefix;
00810         }
00811 
00813         if ( $schema == 'get from global' ) {
00814             $this->mSchema = $wgDBmwschema;
00815         } else {
00816             $this->mSchema = $schema;
00817         }
00818 
00819         $this->mForeign = $foreign;
00820 
00821         if ( $user ) {
00822             $this->open( $server, $user, $password, $dbName );
00823         }
00824     }
00825 
00831     public function __sleep() {
00832         throw new MWException( 'Database serialization may cause problems, since ' .
00833             'the connection is not restored on wakeup.' );
00834     }
00835 
00858     final public static function factory( $dbType, $p = array() ) {
00859         $canonicalDBTypes = array(
00860             'mysql' => array( 'mysqli', 'mysql' ),
00861             'postgres' => array(),
00862             'sqlite' => array(),
00863             'oracle' => array(),
00864             'mssql' => array(),
00865         );
00866 
00867         $driver = false;
00868         $dbType = strtolower( $dbType );
00869         if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
00870             $possibleDrivers = $canonicalDBTypes[$dbType];
00871             if ( !empty( $p['driver'] ) ) {
00872                 if ( in_array( $p['driver'], $possibleDrivers ) ) {
00873                     $driver = $p['driver'];
00874                 } else {
00875                     throw new MWException( __METHOD__ .
00876                         " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
00877                 }
00878             } else {
00879                 foreach ( $possibleDrivers as $posDriver ) {
00880                     if ( extension_loaded( $posDriver ) ) {
00881                         $driver = $posDriver;
00882                         break;
00883                     }
00884                 }
00885             }
00886         } else {
00887             $driver = $dbType;
00888         }
00889         if ( $driver === false ) {
00890             throw new MWException( __METHOD__ .
00891                 " no viable database extension found for type '$dbType'" );
00892         }
00893 
00894         // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
00895         // and everything else doesn't use a schema (e.g. null)
00896         // Although postgres and oracle support schemas, we don't use them (yet)
00897         // to maintain backwards compatibility
00898         $defaultSchemas = array(
00899             'mysql' => null,
00900             'postgres' => null,
00901             'sqlite' => null,
00902             'oracle' => null,
00903             'mssql' => 'get from global',
00904         );
00905 
00906         $class = 'Database' . ucfirst( $driver );
00907         if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
00908             $params = array(
00909                 'host' => isset( $p['host'] ) ? $p['host'] : false,
00910                 'user' => isset( $p['user'] ) ? $p['user'] : false,
00911                 'password' => isset( $p['password'] ) ? $p['password'] : false,
00912                 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false,
00913                 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0,
00914                 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
00915                 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType],
00916                 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false
00917             );
00918 
00919             return new $class( $params );
00920         } else {
00921             return null;
00922         }
00923     }
00924 
00925     protected function installErrorHandler() {
00926         $this->mPHPError = false;
00927         $this->htmlErrors = ini_set( 'html_errors', '0' );
00928         set_error_handler( array( $this, 'connectionErrorHandler' ) );
00929     }
00930 
00934     protected function restoreErrorHandler() {
00935         restore_error_handler();
00936         if ( $this->htmlErrors !== false ) {
00937             ini_set( 'html_errors', $this->htmlErrors );
00938         }
00939         if ( $this->mPHPError ) {
00940             $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00941             $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
00942 
00943             return $error;
00944         } else {
00945             return false;
00946         }
00947     }
00948 
00953     public function connectionErrorHandler( $errno, $errstr ) {
00954         $this->mPHPError = $errstr;
00955     }
00956 
00964     public function close() {
00965         if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
00966             throw new MWException( "Transaction idle callbacks still pending." );
00967         }
00968         if ( $this->mConn ) {
00969             if ( $this->trxLevel() ) {
00970                 if ( !$this->mTrxAutomatic ) {
00971                     wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
00972                         " performing implicit commit before closing connection!" );
00973                 }
00974 
00975                 $this->commit( __METHOD__, 'flush' );
00976             }
00977 
00978             $closed = $this->closeConnection();
00979             $this->mConn = false;
00980         } else {
00981             $closed = true;
00982         }
00983         $this->mOpened = false;
00984 
00985         return $closed;
00986     }
00987 
00993     abstract protected function closeConnection();
00994 
00999     function reportConnectionError( $error = 'Unknown error' ) {
01000         $myError = $this->lastError();
01001         if ( $myError ) {
01002             $error = $myError;
01003         }
01004 
01005         # New method
01006         throw new DBConnectionError( $this, $error );
01007     }
01008 
01016     abstract protected function doQuery( $sql );
01017 
01025     public function isWriteQuery( $sql ) {
01026         return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
01027     }
01028 
01051     public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
01052         global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
01053 
01054         $this->mLastQuery = $sql;
01055         if ( $this->isWriteQuery( $sql ) ) {
01056             # Set a flag indicating that writes have been done
01057             wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
01058             $this->mDoneWrites = microtime( true );
01059         }
01060 
01061         # Add a comment for easy SHOW PROCESSLIST interpretation
01062         if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
01063             $userName = $wgUser->getName();
01064             if ( mb_strlen( $userName ) > 15 ) {
01065                 $userName = mb_substr( $userName, 0, 15 ) . '...';
01066             }
01067             $userName = str_replace( '/', '', $userName );
01068         } else {
01069             $userName = '';
01070         }
01071 
01072         // Add trace comment to the begin of the sql string, right after the operator.
01073         // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
01074         $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
01075 
01076         # If DBO_TRX is set, start a transaction
01077         if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
01078             $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
01079         ) {
01080             # Avoid establishing transactions for SHOW and SET statements too -
01081             # that would delay transaction initializations to once connection
01082             # is really used by application
01083             $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
01084             if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
01085                 if ( $wgDebugDBTransactions ) {
01086                     wfDebug( "Implicit transaction start.\n" );
01087                 }
01088                 $this->begin( __METHOD__ . " ($fname)" );
01089                 $this->mTrxAutomatic = true;
01090             }
01091         }
01092 
01093         # Keep track of whether the transaction has write queries pending
01094         if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
01095             $this->mTrxDoneWrites = true;
01096             Profiler::instance()->transactionWritingIn(
01097                 $this->mServer, $this->mDBname, $this->mTrxShortId );
01098         }
01099 
01100         $queryProf = '';
01101         $totalProf = '';
01102         $isMaster = !is_null( $this->getLBInfo( 'master' ) );
01103 
01104         if ( !Profiler::instance()->isStub() ) {
01105             # generalizeSQL will probably cut down the query to reasonable
01106             # logging size most of the time. The substr is really just a sanity check.
01107             if ( $isMaster ) {
01108                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
01109                 $totalProf = 'DatabaseBase::query-master';
01110             } else {
01111                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
01112                 $totalProf = 'DatabaseBase::query';
01113             }
01114             # Include query transaction state
01115             $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
01116 
01117             $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no';
01118             wfProfileIn( $totalProf );
01119             wfProfileIn( $queryProf );
01120         }
01121 
01122         if ( $this->debug() ) {
01123             static $cnt = 0;
01124 
01125             $cnt++;
01126             $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
01127                 : $commentedSql;
01128             $sqlx = strtr( $sqlx, "\t\n", '  ' );
01129 
01130             $master = $isMaster ? 'master' : 'slave';
01131             wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
01132         }
01133 
01134         $queryId = MWDebug::query( $sql, $fname, $isMaster );
01135 
01136         # Avoid fatals if close() was called
01137         if ( !$this->isOpen() ) {
01138             throw new DBUnexpectedError( $this, "DB connection was already closed." );
01139         }
01140 
01141         # Do the query and handle errors
01142         $ret = $this->doQuery( $commentedSql );
01143 
01144         MWDebug::queryTime( $queryId );
01145 
01146         # Try reconnecting if the connection was lost
01147         if ( false === $ret && $this->wasErrorReissuable() ) {
01148             # Transaction is gone, like it or not
01149             $hadTrx = $this->mTrxLevel; // possible lost transaction
01150             $this->mTrxLevel = 0;
01151             $this->mTrxIdleCallbacks = array(); // bug 65263
01152             $this->mTrxPreCommitCallbacks = array(); // bug 65263
01153             wfDebug( "Connection lost, reconnecting...\n" );
01154             # Stash the last error values since ping() might clear them
01155             $lastError = $this->lastError();
01156             $lastErrno = $this->lastErrno();
01157             if ( $this->ping() ) {
01158                 global $wgRequestTime;
01159                 wfDebug( "Reconnected\n" );
01160                 $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
01161                     : $commentedSql;
01162                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
01163                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
01164                 if ( $elapsed < 300 ) {
01165                     # Not a database error to lose a transaction after a minute or two
01166                     wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" );
01167                 }
01168                 if ( $hadTrx ) {
01169                     # Leave $ret as false and let an error be reported.
01170                     # Callers may catch the exception and continue to use the DB.
01171                     $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
01172                 } else {
01173                     # Should be safe to silently retry (no trx and thus no callbacks)
01174                     $ret = $this->doQuery( $commentedSql );
01175                 }
01176             } else {
01177                 wfDebug( "Failed\n" );
01178             }
01179         }
01180 
01181         if ( false === $ret ) {
01182             $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
01183         }
01184 
01185         if ( !Profiler::instance()->isStub() ) {
01186             wfProfileOut( $queryProf );
01187             wfProfileOut( $totalProf );
01188         }
01189 
01190         return $this->resultObject( $ret );
01191     }
01192 
01204     public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
01205         # Ignore errors during error handling to avoid infinite recursion
01206         $ignore = $this->ignoreErrors( true );
01207         ++$this->mErrorCount;
01208 
01209         if ( $ignore || $tempIgnore ) {
01210             wfDebug( "SQL ERROR (ignored): $error\n" );
01211             $this->ignoreErrors( $ignore );
01212         } else {
01213             $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
01214             wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
01215             wfDebug( "SQL ERROR: " . $error . "\n" );
01216             throw new DBQueryError( $this, $error, $errno, $sql, $fname );
01217         }
01218     }
01219 
01234     protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
01235         /* MySQL doesn't support prepared statements (yet), so just
01236          * pack up the query for reference. We'll manually replace
01237          * the bits later.
01238          */
01239         return array( 'query' => $sql, 'func' => $func );
01240     }
01241 
01246     protected function freePrepared( $prepared ) {
01247         /* No-op by default */
01248     }
01249 
01257     public function execute( $prepared, $args = null ) {
01258         if ( !is_array( $args ) ) {
01259             # Pull the var args
01260             $args = func_get_args();
01261             array_shift( $args );
01262         }
01263 
01264         $sql = $this->fillPrepared( $prepared['query'], $args );
01265 
01266         return $this->query( $sql, $prepared['func'] );
01267     }
01268 
01276     public function fillPrepared( $preparedQuery, $args ) {
01277         reset( $args );
01278         $this->preparedArgs =& $args;
01279 
01280         return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01281             array( &$this, 'fillPreparedArg' ), $preparedQuery );
01282     }
01283 
01293     protected function fillPreparedArg( $matches ) {
01294         switch ( $matches[1] ) {
01295             case '\\?':
01296                 return '?';
01297             case '\\!':
01298                 return '!';
01299             case '\\&':
01300                 return '&';
01301         }
01302 
01303         list( /* $n */, $arg ) = each( $this->preparedArgs );
01304 
01305         switch ( $matches[1] ) {
01306             case '?':
01307                 return $this->addQuotes( $arg );
01308             case '!':
01309                 return $arg;
01310             case '&':
01311                 # return $this->addQuotes( file_get_contents( $arg ) );
01312                 throw new DBUnexpectedError(
01313                     $this,
01314                     '& mode is not implemented. If it\'s really needed, uncomment the line above.'
01315                 );
01316             default:
01317                 throw new DBUnexpectedError(
01318                     $this,
01319                     'Received invalid match. This should never happen!'
01320                 );
01321         }
01322     }
01323 
01331     public function freeResult( $res ) {
01332     }
01333 
01351     public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
01352         $options = array()
01353     ) {
01354         if ( !is_array( $options ) ) {
01355             $options = array( $options );
01356         }
01357 
01358         $options['LIMIT'] = 1;
01359 
01360         $res = $this->select( $table, $var, $cond, $fname, $options );
01361 
01362         if ( $res === false || !$this->numRows( $res ) ) {
01363             return false;
01364         }
01365 
01366         $row = $this->fetchRow( $res );
01367 
01368         if ( $row !== false ) {
01369             return reset( $row );
01370         } else {
01371             return false;
01372         }
01373     }
01374 
01384     public function makeSelectOptions( $options ) {
01385         $preLimitTail = $postLimitTail = '';
01386         $startOpts = '';
01387 
01388         $noKeyOptions = array();
01389 
01390         foreach ( $options as $key => $option ) {
01391             if ( is_numeric( $key ) ) {
01392                 $noKeyOptions[$option] = true;
01393             }
01394         }
01395 
01396         $preLimitTail .= $this->makeGroupByWithHaving( $options );
01397 
01398         $preLimitTail .= $this->makeOrderBy( $options );
01399 
01400         // if (isset($options['LIMIT'])) {
01401         //  $tailOpts .= $this->limitResult('', $options['LIMIT'],
01402         //      isset($options['OFFSET']) ? $options['OFFSET']
01403         //      : false);
01404         // }
01405 
01406         if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01407             $postLimitTail .= ' FOR UPDATE';
01408         }
01409 
01410         if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01411             $postLimitTail .= ' LOCK IN SHARE MODE';
01412         }
01413 
01414         if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01415             $startOpts .= 'DISTINCT';
01416         }
01417 
01418         # Various MySQL extensions
01419         if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01420             $startOpts .= ' /*! STRAIGHT_JOIN */';
01421         }
01422 
01423         if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01424             $startOpts .= ' HIGH_PRIORITY';
01425         }
01426 
01427         if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01428             $startOpts .= ' SQL_BIG_RESULT';
01429         }
01430 
01431         if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01432             $startOpts .= ' SQL_BUFFER_RESULT';
01433         }
01434 
01435         if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01436             $startOpts .= ' SQL_SMALL_RESULT';
01437         }
01438 
01439         if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01440             $startOpts .= ' SQL_CALC_FOUND_ROWS';
01441         }
01442 
01443         if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01444             $startOpts .= ' SQL_CACHE';
01445         }
01446 
01447         if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01448             $startOpts .= ' SQL_NO_CACHE';
01449         }
01450 
01451         if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
01452             $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01453         } else {
01454             $useIndex = '';
01455         }
01456 
01457         return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01458     }
01459 
01468     public function makeGroupByWithHaving( $options ) {
01469         $sql = '';
01470         if ( isset( $options['GROUP BY'] ) ) {
01471             $gb = is_array( $options['GROUP BY'] )
01472                 ? implode( ',', $options['GROUP BY'] )
01473                 : $options['GROUP BY'];
01474             $sql .= ' GROUP BY ' . $gb;
01475         }
01476         if ( isset( $options['HAVING'] ) ) {
01477             $having = is_array( $options['HAVING'] )
01478                 ? $this->makeList( $options['HAVING'], LIST_AND )
01479                 : $options['HAVING'];
01480             $sql .= ' HAVING ' . $having;
01481         }
01482 
01483         return $sql;
01484     }
01485 
01494     public function makeOrderBy( $options ) {
01495         if ( isset( $options['ORDER BY'] ) ) {
01496             $ob = is_array( $options['ORDER BY'] )
01497                 ? implode( ',', $options['ORDER BY'] )
01498                 : $options['ORDER BY'];
01499 
01500             return ' ORDER BY ' . $ob;
01501         }
01502 
01503         return '';
01504     }
01505 
01646     public function select( $table, $vars, $conds = '', $fname = __METHOD__,
01647         $options = array(), $join_conds = array() ) {
01648         $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01649 
01650         return $this->query( $sql, $fname );
01651     }
01652 
01669     public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
01670         $options = array(), $join_conds = array()
01671     ) {
01672         if ( is_array( $vars ) ) {
01673             $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
01674         }
01675 
01676         $options = (array)$options;
01677         $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01678             ? $options['USE INDEX']
01679             : array();
01680 
01681         if ( is_array( $table ) ) {
01682             $from = ' FROM ' .
01683                 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
01684         } elseif ( $table != '' ) {
01685             if ( $table[0] == ' ' ) {
01686                 $from = ' FROM ' . $table;
01687             } else {
01688                 $from = ' FROM ' .
01689                     $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() );
01690             }
01691         } else {
01692             $from = '';
01693         }
01694 
01695         list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
01696             $this->makeSelectOptions( $options );
01697 
01698         if ( !empty( $conds ) ) {
01699             if ( is_array( $conds ) ) {
01700                 $conds = $this->makeList( $conds, LIST_AND );
01701             }
01702             $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01703         } else {
01704             $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01705         }
01706 
01707         if ( isset( $options['LIMIT'] ) ) {
01708             $sql = $this->limitResult( $sql, $options['LIMIT'],
01709                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01710         }
01711         $sql = "$sql $postLimitTail";
01712 
01713         if ( isset( $options['EXPLAIN'] ) ) {
01714             $sql = 'EXPLAIN ' . $sql;
01715         }
01716 
01717         return $sql;
01718     }
01719 
01734     public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
01735         $options = array(), $join_conds = array()
01736     ) {
01737         $options = (array)$options;
01738         $options['LIMIT'] = 1;
01739         $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01740 
01741         if ( $res === false ) {
01742             return false;
01743         }
01744 
01745         if ( !$this->numRows( $res ) ) {
01746             return false;
01747         }
01748 
01749         $obj = $this->fetchObject( $res );
01750 
01751         return $obj;
01752     }
01753 
01774     public function estimateRowCount(
01775         $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
01776     ) {
01777         $rows = 0;
01778         $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
01779 
01780         if ( $res ) {
01781             $row = $this->fetchRow( $res );
01782             $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01783         }
01784 
01785         return $rows;
01786     }
01787 
01803     public function selectRowCount(
01804         $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
01805     ) {
01806         $rows = 0;
01807         $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options );
01808         $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" );
01809 
01810         if ( $res ) {
01811             $row = $this->fetchRow( $res );
01812             $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01813         }
01814 
01815         return $rows;
01816     }
01817 
01826     static function generalizeSQL( $sql ) {
01827         # This does the same as the regexp below would do, but in such a way
01828         # as to avoid crashing php on some large strings.
01829         # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
01830 
01831         $sql = str_replace( "\\\\", '', $sql );
01832         $sql = str_replace( "\\'", '', $sql );
01833         $sql = str_replace( "\\\"", '', $sql );
01834         $sql = preg_replace( "/'.*'/s", "'X'", $sql );
01835         $sql = preg_replace( '/".*"/s', "'X'", $sql );
01836 
01837         # All newlines, tabs, etc replaced by single space
01838         $sql = preg_replace( '/\s+/', ' ', $sql );
01839 
01840         # All numbers => N,
01841         # except the ones surrounded by characters, e.g. l10n
01842         $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
01843         $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
01844 
01845         return $sql;
01846     }
01847 
01856     public function fieldExists( $table, $field, $fname = __METHOD__ ) {
01857         $info = $this->fieldInfo( $table, $field );
01858 
01859         return (bool)$info;
01860     }
01861 
01872     public function indexExists( $table, $index, $fname = __METHOD__ ) {
01873         if ( !$this->tableExists( $table ) ) {
01874             return null;
01875         }
01876 
01877         $info = $this->indexInfo( $table, $index, $fname );
01878         if ( is_null( $info ) ) {
01879             return null;
01880         } else {
01881             return $info !== false;
01882         }
01883     }
01884 
01892     public function tableExists( $table, $fname = __METHOD__ ) {
01893         $table = $this->tableName( $table );
01894         $old = $this->ignoreErrors( true );
01895         $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01896         $this->ignoreErrors( $old );
01897 
01898         return (bool)$res;
01899     }
01900 
01909     public function indexUnique( $table, $index ) {
01910         $indexInfo = $this->indexInfo( $table, $index );
01911 
01912         if ( !$indexInfo ) {
01913             return null;
01914         }
01915 
01916         return !$indexInfo[0]->Non_unique;
01917     }
01918 
01925     protected function makeInsertOptions( $options ) {
01926         return implode( ' ', $options );
01927     }
01928 
01962     public function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
01963         # No rows to insert, easy just return now
01964         if ( !count( $a ) ) {
01965             return true;
01966         }
01967 
01968         $table = $this->tableName( $table );
01969 
01970         if ( !is_array( $options ) ) {
01971             $options = array( $options );
01972         }
01973 
01974         $fh = null;
01975         if ( isset( $options['fileHandle'] ) ) {
01976             $fh = $options['fileHandle'];
01977         }
01978         $options = $this->makeInsertOptions( $options );
01979 
01980         if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01981             $multi = true;
01982             $keys = array_keys( $a[0] );
01983         } else {
01984             $multi = false;
01985             $keys = array_keys( $a );
01986         }
01987 
01988         $sql = 'INSERT ' . $options .
01989             " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01990 
01991         if ( $multi ) {
01992             $first = true;
01993             foreach ( $a as $row ) {
01994                 if ( $first ) {
01995                     $first = false;
01996                 } else {
01997                     $sql .= ',';
01998                 }
01999                 $sql .= '(' . $this->makeList( $row ) . ')';
02000             }
02001         } else {
02002             $sql .= '(' . $this->makeList( $a ) . ')';
02003         }
02004 
02005         if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
02006             return false;
02007         } elseif ( $fh !== null ) {
02008             return true;
02009         }
02010 
02011         return (bool)$this->query( $sql, $fname );
02012     }
02013 
02020     protected function makeUpdateOptionsArray( $options ) {
02021         if ( !is_array( $options ) ) {
02022             $options = array( $options );
02023         }
02024 
02025         $opts = array();
02026 
02027         if ( in_array( 'LOW_PRIORITY', $options ) ) {
02028             $opts[] = $this->lowPriorityOption();
02029         }
02030 
02031         if ( in_array( 'IGNORE', $options ) ) {
02032             $opts[] = 'IGNORE';
02033         }
02034 
02035         return $opts;
02036     }
02037 
02044     protected function makeUpdateOptions( $options ) {
02045         $opts = $this->makeUpdateOptionsArray( $options );
02046 
02047         return implode( ' ', $opts );
02048     }
02049 
02068     function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
02069         $table = $this->tableName( $table );
02070         $opts = $this->makeUpdateOptions( $options );
02071         $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
02072 
02073         if ( $conds !== array() && $conds !== '*' ) {
02074             $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
02075         }
02076 
02077         return $this->query( $sql, $fname );
02078     }
02079 
02094     public function makeList( $a, $mode = LIST_COMMA ) {
02095         if ( !is_array( $a ) ) {
02096             throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
02097         }
02098 
02099         $first = true;
02100         $list = '';
02101 
02102         foreach ( $a as $field => $value ) {
02103             if ( !$first ) {
02104                 if ( $mode == LIST_AND ) {
02105                     $list .= ' AND ';
02106                 } elseif ( $mode == LIST_OR ) {
02107                     $list .= ' OR ';
02108                 } else {
02109                     $list .= ',';
02110                 }
02111             } else {
02112                 $first = false;
02113             }
02114 
02115             if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
02116                 $list .= "($value)";
02117             } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
02118                 $list .= "$value";
02119             } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
02120                 if ( count( $value ) == 0 ) {
02121                     throw new MWException( __METHOD__ . ": empty input for field $field" );
02122                 } elseif ( count( $value ) == 1 ) {
02123                     // Special-case single values, as IN isn't terribly efficient
02124                     // Don't necessarily assume the single key is 0; we don't
02125                     // enforce linear numeric ordering on other arrays here.
02126                     $value = array_values( $value );
02127                     $list .= $field . " = " . $this->addQuotes( $value[0] );
02128                 } else {
02129                     $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
02130                 }
02131             } elseif ( $value === null ) {
02132                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
02133                     $list .= "$field IS ";
02134                 } elseif ( $mode == LIST_SET ) {
02135                     $list .= "$field = ";
02136                 }
02137                 $list .= 'NULL';
02138             } else {
02139                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
02140                     $list .= "$field = ";
02141                 }
02142                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
02143             }
02144         }
02145 
02146         return $list;
02147     }
02148 
02159     public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
02160         $conds = array();
02161 
02162         foreach ( $data as $base => $sub ) {
02163             if ( count( $sub ) ) {
02164                 $conds[] = $this->makeList(
02165                     array( $baseKey => $base, $subKey => array_keys( $sub ) ),
02166                     LIST_AND );
02167             }
02168         }
02169 
02170         if ( $conds ) {
02171             return $this->makeList( $conds, LIST_OR );
02172         } else {
02173             // Nothing to search for...
02174             return false;
02175         }
02176     }
02177 
02186     public function aggregateValue( $valuedata, $valuename = 'value' ) {
02187         return $valuename;
02188     }
02189 
02194     public function bitNot( $field ) {
02195         return "(~$field)";
02196     }
02197 
02203     public function bitAnd( $fieldLeft, $fieldRight ) {
02204         return "($fieldLeft & $fieldRight)";
02205     }
02206 
02212     public function bitOr( $fieldLeft, $fieldRight ) {
02213         return "($fieldLeft | $fieldRight)";
02214     }
02215 
02222     public function buildConcat( $stringList ) {
02223         return 'CONCAT(' . implode( ',', $stringList ) . ')';
02224     }
02225 
02242     public function buildGroupConcatField(
02243         $delim, $table, $field, $conds = '', $join_conds = array()
02244     ) {
02245         $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
02246 
02247         return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
02248     }
02249 
02259     public function selectDB( $db ) {
02260         # Stub. Shouldn't cause serious problems if it's not overridden, but
02261         # if your database engine supports a concept similar to MySQL's
02262         # databases you may as well.
02263         $this->mDBname = $db;
02264 
02265         return true;
02266     }
02267 
02272     public function getDBname() {
02273         return $this->mDBname;
02274     }
02275 
02280     public function getServer() {
02281         return $this->mServer;
02282     }
02283 
02301     public function tableName( $name, $format = 'quoted' ) {
02302         global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema;
02303         # Skip the entire process when we have a string quoted on both ends.
02304         # Note that we check the end so that we will still quote any use of
02305         # use of `database`.table. But won't break things if someone wants
02306         # to query a database table with a dot in the name.
02307         if ( $this->isQuotedIdentifier( $name ) ) {
02308             return $name;
02309         }
02310 
02311         # Lets test for any bits of text that should never show up in a table
02312         # name. Basically anything like JOIN or ON which are actually part of
02313         # SQL queries, but may end up inside of the table value to combine
02314         # sql. Such as how the API is doing.
02315         # Note that we use a whitespace test rather than a \b test to avoid
02316         # any remote case where a word like on may be inside of a table name
02317         # surrounded by symbols which may be considered word breaks.
02318         if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
02319             return $name;
02320         }
02321 
02322         # Split database and table into proper variables.
02323         # We reverse the explode so that database.table and table both output
02324         # the correct table.
02325         $dbDetails = explode( '.', $name, 2 );
02326         if ( count( $dbDetails ) == 3 ) {
02327             list( $database, $schema, $table ) = $dbDetails;
02328             # We don't want any prefix added in this case
02329             $prefix = '';
02330         } elseif ( count( $dbDetails ) == 2 ) {
02331             list( $database, $table ) = $dbDetails;
02332             # We don't want any prefix added in this case
02333             # In dbs that support it, $database may actually be the schema
02334             # but that doesn't affect any of the functionality here
02335             $prefix = '';
02336             $schema = null;
02337         } else {
02338             list( $table ) = $dbDetails;
02339             if ( $wgSharedDB !== null # We have a shared database
02340                 && $this->mForeign == false # We're not working on a foreign database
02341                 && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
02342                 && in_array( $table, $wgSharedTables ) # A shared table is selected
02343             ) {
02344                 $database = $wgSharedDB;
02345                 $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
02346                 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
02347             } else {
02348                 $database = null;
02349                 $schema = $this->mSchema; # Default schema
02350                 $prefix = $this->mTablePrefix; # Default prefix
02351             }
02352         }
02353 
02354         # Quote $table and apply the prefix if not quoted.
02355         # $tableName might be empty if this is called from Database::replaceVars()
02356         $tableName = "{$prefix}{$table}";
02357         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
02358             $tableName = $this->addIdentifierQuotes( $tableName );
02359         }
02360 
02361         # Quote $schema and merge it with the table name if needed
02362         if ( $schema !== null ) {
02363             if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
02364                 $schema = $this->addIdentifierQuotes( $schema );
02365             }
02366             $tableName = $schema . '.' . $tableName;
02367         }
02368 
02369         # Quote $database and merge it with the table name if needed
02370         if ( $database !== null ) {
02371             if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
02372                 $database = $this->addIdentifierQuotes( $database );
02373             }
02374             $tableName = $database . '.' . $tableName;
02375         }
02376 
02377         return $tableName;
02378     }
02379 
02391     public function tableNames() {
02392         $inArray = func_get_args();
02393         $retVal = array();
02394 
02395         foreach ( $inArray as $name ) {
02396             $retVal[$name] = $this->tableName( $name );
02397         }
02398 
02399         return $retVal;
02400     }
02401 
02413     public function tableNamesN() {
02414         $inArray = func_get_args();
02415         $retVal = array();
02416 
02417         foreach ( $inArray as $name ) {
02418             $retVal[] = $this->tableName( $name );
02419         }
02420 
02421         return $retVal;
02422     }
02423 
02432     public function tableNameWithAlias( $name, $alias = false ) {
02433         if ( !$alias || $alias == $name ) {
02434             return $this->tableName( $name );
02435         } else {
02436             return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02437         }
02438     }
02439 
02446     public function tableNamesWithAlias( $tables ) {
02447         $retval = array();
02448         foreach ( $tables as $alias => $table ) {
02449             if ( is_numeric( $alias ) ) {
02450                 $alias = $table;
02451             }
02452             $retval[] = $this->tableNameWithAlias( $table, $alias );
02453         }
02454 
02455         return $retval;
02456     }
02457 
02466     public function fieldNameWithAlias( $name, $alias = false ) {
02467         if ( !$alias || (string)$alias === (string)$name ) {
02468             return $name;
02469         } else {
02470             return $name . ' AS ' . $alias; //PostgreSQL needs AS
02471         }
02472     }
02473 
02480     public function fieldNamesWithAlias( $fields ) {
02481         $retval = array();
02482         foreach ( $fields as $alias => $field ) {
02483             if ( is_numeric( $alias ) ) {
02484                 $alias = $field;
02485             }
02486             $retval[] = $this->fieldNameWithAlias( $field, $alias );
02487         }
02488 
02489         return $retval;
02490     }
02491 
02501     protected function tableNamesWithUseIndexOrJOIN(
02502         $tables, $use_index = array(), $join_conds = array()
02503     ) {
02504         $ret = array();
02505         $retJOIN = array();
02506         $use_index = (array)$use_index;
02507         $join_conds = (array)$join_conds;
02508 
02509         foreach ( $tables as $alias => $table ) {
02510             if ( !is_string( $alias ) ) {
02511                 // No alias? Set it equal to the table name
02512                 $alias = $table;
02513             }
02514             // Is there a JOIN clause for this table?
02515             if ( isset( $join_conds[$alias] ) ) {
02516                 list( $joinType, $conds ) = $join_conds[$alias];
02517                 $tableClause = $joinType;
02518                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02519                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02520                     $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02521                     if ( $use != '' ) {
02522                         $tableClause .= ' ' . $use;
02523                     }
02524                 }
02525                 $on = $this->makeList( (array)$conds, LIST_AND );
02526                 if ( $on != '' ) {
02527                     $tableClause .= ' ON (' . $on . ')';
02528                 }
02529 
02530                 $retJOIN[] = $tableClause;
02531             } elseif ( isset( $use_index[$alias] ) ) {
02532                 // Is there an INDEX clause for this table?
02533                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02534                 $tableClause .= ' ' . $this->useIndexClause(
02535                     implode( ',', (array)$use_index[$alias] )
02536                 );
02537 
02538                 $ret[] = $tableClause;
02539             } else {
02540                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02541 
02542                 $ret[] = $tableClause;
02543             }
02544         }
02545 
02546         // We can't separate explicit JOIN clauses with ',', use ' ' for those
02547         $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02548         $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02549 
02550         // Compile our final table clause
02551         return implode( ' ', array( $implicitJoins, $explicitJoins ) );
02552     }
02553 
02560     protected function indexName( $index ) {
02561         // Backwards-compatibility hack
02562         $renamed = array(
02563             'ar_usertext_timestamp' => 'usertext_timestamp',
02564             'un_user_id' => 'user_id',
02565             'un_user_ip' => 'user_ip',
02566         );
02567 
02568         if ( isset( $renamed[$index] ) ) {
02569             return $renamed[$index];
02570         } else {
02571             return $index;
02572         }
02573     }
02574 
02581     public function addQuotes( $s ) {
02582         if ( $s === null ) {
02583             return 'NULL';
02584         } else {
02585             # This will also quote numeric values. This should be harmless,
02586             # and protects against weird problems that occur when they really
02587             # _are_ strings such as article titles and string->number->string
02588             # conversion is not 1:1.
02589             return "'" . $this->strencode( $s ) . "'";
02590         }
02591     }
02592 
02602     public function addIdentifierQuotes( $s ) {
02603         return '"' . str_replace( '"', '""', $s ) . '"';
02604     }
02605 
02613     public function isQuotedIdentifier( $name ) {
02614         return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02615     }
02616 
02621     protected function escapeLikeInternal( $s ) {
02622         return addcslashes( $s, '\%_' );
02623     }
02624 
02641     public function buildLike() {
02642         $params = func_get_args();
02643 
02644         if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02645             $params = $params[0];
02646         }
02647 
02648         $s = '';
02649 
02650         foreach ( $params as $value ) {
02651             if ( $value instanceof LikeMatch ) {
02652                 $s .= $value->toString();
02653             } else {
02654                 $s .= $this->escapeLikeInternal( $value );
02655             }
02656         }
02657 
02658         return " LIKE {$this->addQuotes( $s )} ";
02659     }
02660 
02666     public function anyChar() {
02667         return new LikeMatch( '_' );
02668     }
02669 
02675     public function anyString() {
02676         return new LikeMatch( '%' );
02677     }
02678 
02690     public function nextSequenceValue( $seqName ) {
02691         return null;
02692     }
02693 
02704     public function useIndexClause( $index ) {
02705         return '';
02706     }
02707 
02730     public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
02731         $quotedTable = $this->tableName( $table );
02732 
02733         if ( count( $rows ) == 0 ) {
02734             return;
02735         }
02736 
02737         # Single row case
02738         if ( !is_array( reset( $rows ) ) ) {
02739             $rows = array( $rows );
02740         }
02741 
02742         foreach ( $rows as $row ) {
02743             # Delete rows which collide
02744             if ( $uniqueIndexes ) {
02745                 $sql = "DELETE FROM $quotedTable WHERE ";
02746                 $first = true;
02747                 foreach ( $uniqueIndexes as $index ) {
02748                     if ( $first ) {
02749                         $first = false;
02750                         $sql .= '( ';
02751                     } else {
02752                         $sql .= ' ) OR ( ';
02753                     }
02754                     if ( is_array( $index ) ) {
02755                         $first2 = true;
02756                         foreach ( $index as $col ) {
02757                             if ( $first2 ) {
02758                                 $first2 = false;
02759                             } else {
02760                                 $sql .= ' AND ';
02761                             }
02762                             $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02763                         }
02764                     } else {
02765                         $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02766                     }
02767                 }
02768                 $sql .= ' )';
02769                 $this->query( $sql, $fname );
02770             }
02771 
02772             # Now insert the row
02773             $this->insert( $table, $row, $fname );
02774         }
02775     }
02776 
02787     protected function nativeReplace( $table, $rows, $fname ) {
02788         $table = $this->tableName( $table );
02789 
02790         # Single row case
02791         if ( !is_array( reset( $rows ) ) ) {
02792             $rows = array( $rows );
02793         }
02794 
02795         $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02796         $first = true;
02797 
02798         foreach ( $rows as $row ) {
02799             if ( $first ) {
02800                 $first = false;
02801             } else {
02802                 $sql .= ',';
02803             }
02804 
02805             $sql .= '(' . $this->makeList( $row ) . ')';
02806         }
02807 
02808         return $this->query( $sql, $fname );
02809     }
02810 
02845     public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
02846         $fname = __METHOD__
02847     ) {
02848         if ( !count( $rows ) ) {
02849             return true; // nothing to do
02850         }
02851 
02852         if ( !is_array( reset( $rows ) ) ) {
02853             $rows = array( $rows );
02854         }
02855 
02856         if ( count( $uniqueIndexes ) ) {
02857             $clauses = array(); // list WHERE clauses that each identify a single row
02858             foreach ( $rows as $row ) {
02859                 foreach ( $uniqueIndexes as $index ) {
02860                     $index = is_array( $index ) ? $index : array( $index ); // columns
02861                     $rowKey = array(); // unique key to this row
02862                     foreach ( $index as $column ) {
02863                         $rowKey[$column] = $row[$column];
02864                     }
02865                     $clauses[] = $this->makeList( $rowKey, LIST_AND );
02866                 }
02867             }
02868             $where = array( $this->makeList( $clauses, LIST_OR ) );
02869         } else {
02870             $where = false;
02871         }
02872 
02873         $useTrx = !$this->mTrxLevel;
02874         if ( $useTrx ) {
02875             $this->begin( $fname );
02876         }
02877         try {
02878             # Update any existing conflicting row(s)
02879             if ( $where !== false ) {
02880                 $ok = $this->update( $table, $set, $where, $fname );
02881             } else {
02882                 $ok = true;
02883             }
02884             # Now insert any non-conflicting row(s)
02885             $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok;
02886         } catch ( Exception $e ) {
02887             if ( $useTrx ) {
02888                 $this->rollback( $fname );
02889             }
02890             throw $e;
02891         }
02892         if ( $useTrx ) {
02893             $this->commit( $fname );
02894         }
02895 
02896         return $ok;
02897     }
02898 
02919     public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02920         $fname = __METHOD__
02921     ) {
02922         if ( !$conds ) {
02923             throw new DBUnexpectedError( $this,
02924                 'DatabaseBase::deleteJoin() called with empty $conds' );
02925         }
02926 
02927         $delTable = $this->tableName( $delTable );
02928         $joinTable = $this->tableName( $joinTable );
02929         $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02930         if ( $conds != '*' ) {
02931             $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02932         }
02933         $sql .= ')';
02934 
02935         $this->query( $sql, $fname );
02936     }
02937 
02945     public function textFieldSize( $table, $field ) {
02946         $table = $this->tableName( $table );
02947         $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02948         $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02949         $row = $this->fetchObject( $res );
02950 
02951         $m = array();
02952 
02953         if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02954             $size = $m[1];
02955         } else {
02956             $size = -1;
02957         }
02958 
02959         return $size;
02960     }
02961 
02970     public function lowPriorityOption() {
02971         return '';
02972     }
02973 
02984     public function delete( $table, $conds, $fname = __METHOD__ ) {
02985         if ( !$conds ) {
02986             throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02987         }
02988 
02989         $table = $this->tableName( $table );
02990         $sql = "DELETE FROM $table";
02991 
02992         if ( $conds != '*' ) {
02993             if ( is_array( $conds ) ) {
02994                 $conds = $this->makeList( $conds, LIST_AND );
02995             }
02996             $sql .= ' WHERE ' . $conds;
02997         }
02998 
02999         return $this->query( $sql, $fname );
03000     }
03001 
03028     public function insertSelect( $destTable, $srcTable, $varMap, $conds,
03029         $fname = __METHOD__,
03030         $insertOptions = array(), $selectOptions = array()
03031     ) {
03032         $destTable = $this->tableName( $destTable );
03033 
03034         if ( !is_array( $insertOptions ) ) {
03035             $insertOptions = array( $insertOptions );
03036         }
03037 
03038         $insertOptions = $this->makeInsertOptions( $insertOptions );
03039 
03040         if ( !is_array( $selectOptions ) ) {
03041             $selectOptions = array( $selectOptions );
03042         }
03043 
03044         list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
03045 
03046         if ( is_array( $srcTable ) ) {
03047             $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
03048         } else {
03049             $srcTable = $this->tableName( $srcTable );
03050         }
03051 
03052         $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
03053             " SELECT $startOpts " . implode( ',', $varMap ) .
03054             " FROM $srcTable $useIndex ";
03055 
03056         if ( $conds != '*' ) {
03057             if ( is_array( $conds ) ) {
03058                 $conds = $this->makeList( $conds, LIST_AND );
03059             }
03060             $sql .= " WHERE $conds";
03061         }
03062 
03063         $sql .= " $tailOpts";
03064 
03065         return $this->query( $sql, $fname );
03066     }
03067 
03087     public function limitResult( $sql, $limit, $offset = false ) {
03088         if ( !is_numeric( $limit ) ) {
03089             throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
03090         }
03091 
03092         return "$sql LIMIT "
03093             . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
03094             . "{$limit} ";
03095     }
03096 
03102     public function unionSupportsOrderAndLimit() {
03103         return true; // True for almost every DB supported
03104     }
03105 
03114     public function unionQueries( $sqls, $all ) {
03115         $glue = $all ? ') UNION ALL (' : ') UNION (';
03116 
03117         return '(' . implode( $glue, $sqls ) . ')';
03118     }
03119 
03129     public function conditional( $cond, $trueVal, $falseVal ) {
03130         if ( is_array( $cond ) ) {
03131             $cond = $this->makeList( $cond, LIST_AND );
03132         }
03133 
03134         return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
03135     }
03136 
03147     public function strreplace( $orig, $old, $new ) {
03148         return "REPLACE({$orig}, {$old}, {$new})";
03149     }
03150 
03157     public function getServerUptime() {
03158         return 0;
03159     }
03160 
03167     public function wasDeadlock() {
03168         return false;
03169     }
03170 
03177     public function wasLockTimeout() {
03178         return false;
03179     }
03180 
03188     public function wasErrorReissuable() {
03189         return false;
03190     }
03191 
03198     public function wasReadOnlyError() {
03199         return false;
03200     }
03201 
03220     public function deadlockLoop() {
03221         $this->begin( __METHOD__ );
03222         $args = func_get_args();
03223         $function = array_shift( $args );
03224         $oldIgnore = $this->ignoreErrors( true );
03225         $tries = self::DEADLOCK_TRIES;
03226 
03227         if ( is_array( $function ) ) {
03228             $fname = $function[0];
03229         } else {
03230             $fname = $function;
03231         }
03232 
03233         do {
03234             $retVal = call_user_func_array( $function, $args );
03235             $error = $this->lastError();
03236             $errno = $this->lastErrno();
03237             $sql = $this->lastQuery();
03238 
03239             if ( $errno ) {
03240                 if ( $this->wasDeadlock() ) {
03241                     # Retry
03242                     usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
03243                 } else {
03244                     $this->reportQueryError( $error, $errno, $sql, $fname );
03245                 }
03246             }
03247         } while ( $this->wasDeadlock() && --$tries > 0 );
03248 
03249         $this->ignoreErrors( $oldIgnore );
03250 
03251         if ( $tries <= 0 ) {
03252             $this->rollback( __METHOD__ );
03253             $this->reportQueryError( $error, $errno, $sql, $fname );
03254 
03255             return false;
03256         } else {
03257             $this->commit( __METHOD__ );
03258 
03259             return $retVal;
03260         }
03261     }
03262 
03273     public function masterPosWait( DBMasterPos $pos, $timeout ) {
03274         # Real waits are implemented in the subclass.
03275         return 0;
03276     }
03277 
03283     public function getSlavePos() {
03284         # Stub
03285         return false;
03286     }
03287 
03293     public function getMasterPos() {
03294         # Stub
03295         return false;
03296     }
03297 
03312     final public function onTransactionIdle( $callback ) {
03313         $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() );
03314         if ( !$this->mTrxLevel ) {
03315             $this->runOnTransactionIdleCallbacks();
03316         }
03317     }
03318 
03330     final public function onTransactionPreCommitOrIdle( $callback ) {
03331         if ( $this->mTrxLevel ) {
03332             $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() );
03333         } else {
03334             $this->onTransactionIdle( $callback ); // this will trigger immediately
03335         }
03336     }
03337 
03343     protected function runOnTransactionIdleCallbacks() {
03344         $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
03345 
03346         $e = $ePrior = null; // last exception
03347         do { // callbacks may add callbacks :)
03348             $callbacks = $this->mTrxIdleCallbacks;
03349             $this->mTrxIdleCallbacks = array(); // recursion guard
03350             foreach ( $callbacks as $callback ) {
03351                 try {
03352                     list( $phpCallback ) = $callback;
03353                     $this->clearFlag( DBO_TRX ); // make each query its own transaction
03354                     call_user_func( $phpCallback );
03355                     $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
03356                 } catch ( Exception $e ) {
03357                     if ( $ePrior ) {
03358                         MWExceptionHandler::logException( $ePrior );
03359                     }
03360                     $ePrior = $e;
03361                 }
03362             }
03363         } while ( count( $this->mTrxIdleCallbacks ) );
03364 
03365         if ( $e instanceof Exception ) {
03366             throw $e; // re-throw any last exception
03367         }
03368     }
03369 
03375     protected function runOnTransactionPreCommitCallbacks() {
03376         $e = $ePrior = null; // last exception
03377         do { // callbacks may add callbacks :)
03378             $callbacks = $this->mTrxPreCommitCallbacks;
03379             $this->mTrxPreCommitCallbacks = array(); // recursion guard
03380             foreach ( $callbacks as $callback ) {
03381                 try {
03382                     list( $phpCallback ) = $callback;
03383                     call_user_func( $phpCallback );
03384                 } catch ( Exception $e ) {
03385                     if ( $ePrior ) {
03386                         MWExceptionHandler::logException( $ePrior );
03387                     }
03388                     $ePrior = $e;
03389                 }
03390             }
03391         } while ( count( $this->mTrxPreCommitCallbacks ) );
03392 
03393         if ( $e instanceof Exception ) {
03394             throw $e; // re-throw any last exception
03395         }
03396     }
03397 
03422     final public function startAtomic( $fname = __METHOD__ ) {
03423         if ( !$this->mTrxLevel ) {
03424             $this->begin( $fname );
03425             $this->mTrxAutomatic = true;
03426             $this->mTrxAutomaticAtomic = true;
03427         }
03428 
03429         $this->mTrxAtomicLevels->push( $fname );
03430     }
03431 
03443     final public function endAtomic( $fname = __METHOD__ ) {
03444         if ( !$this->mTrxLevel ) {
03445             throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
03446         }
03447         if ( $this->mTrxAtomicLevels->isEmpty() ||
03448             $this->mTrxAtomicLevels->pop() !== $fname
03449         ) {
03450             throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
03451         }
03452 
03453         if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) {
03454             $this->commit( $fname, 'flush' );
03455         }
03456     }
03457 
03473     final public function begin( $fname = __METHOD__ ) {
03474         global $wgDebugDBTransactions;
03475 
03476         if ( $this->mTrxLevel ) { // implicit commit
03477             if ( !$this->mTrxAtomicLevels->isEmpty() ) {
03478                 // If the current transaction was an automatic atomic one, then we definitely have
03479                 // a problem. Same if there is any unclosed atomic level.
03480                 throw new DBUnexpectedError( $this,
03481                     "Attempted to start explicit transaction when atomic levels are still open."
03482                 );
03483             } elseif ( !$this->mTrxAutomatic ) {
03484                 // We want to warn about inadvertently nested begin/commit pairs, but not about
03485                 // auto-committing implicit transactions that were started by query() via DBO_TRX
03486                 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
03487                     " performing implicit commit!";
03488                 wfWarn( $msg );
03489                 wfLogDBError( $msg );
03490             } else {
03491                 // if the transaction was automatic and has done write operations,
03492                 // log it if $wgDebugDBTransactions is enabled.
03493                 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
03494                     wfDebug( "$fname: Automatic transaction with writes in progress" .
03495                         " (from {$this->mTrxFname}), performing implicit commit!\n"
03496                     );
03497                 }
03498             }
03499 
03500             $this->runOnTransactionPreCommitCallbacks();
03501             $this->doCommit( $fname );
03502             if ( $this->mTrxDoneWrites ) {
03503                 Profiler::instance()->transactionWritingOut(
03504                     $this->mServer, $this->mDBname, $this->mTrxShortId );
03505             }
03506             $this->runOnTransactionIdleCallbacks();
03507         }
03508 
03509         # Avoid fatals if close() was called
03510         if ( !$this->isOpen() ) {
03511             throw new DBUnexpectedError( $this, "DB connection was already closed." );
03512         }
03513 
03514         $this->doBegin( $fname );
03515         $this->mTrxFname = $fname;
03516         $this->mTrxDoneWrites = false;
03517         $this->mTrxAutomatic = false;
03518         $this->mTrxAutomaticAtomic = false;
03519         $this->mTrxAtomicLevels = new SplStack;
03520         $this->mTrxIdleCallbacks = array();
03521         $this->mTrxPreCommitCallbacks = array();
03522         $this->mTrxShortId = wfRandomString( 12 );
03523     }
03524 
03531     protected function doBegin( $fname ) {
03532         $this->query( 'BEGIN', $fname );
03533         $this->mTrxLevel = 1;
03534     }
03535 
03550     final public function commit( $fname = __METHOD__, $flush = '' ) {
03551         if ( !$this->mTrxAtomicLevels->isEmpty() ) {
03552             // There are still atomic sections open. This cannot be ignored
03553             throw new DBUnexpectedError(
03554                 $this,
03555                 "Attempted to commit transaction while atomic sections are still open"
03556             );
03557         }
03558 
03559         if ( $flush === 'flush' ) {
03560             if ( !$this->mTrxLevel ) {
03561                 return; // nothing to do
03562             } elseif ( !$this->mTrxAutomatic ) {
03563                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03564             }
03565         } else {
03566             if ( !$this->mTrxLevel ) {
03567                 wfWarn( "$fname: No transaction to commit, something got out of sync!" );
03568                 return; // nothing to do
03569             } elseif ( $this->mTrxAutomatic ) {
03570                 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
03571             }
03572         }
03573 
03574         # Avoid fatals if close() was called
03575         if ( !$this->isOpen() ) {
03576             throw new DBUnexpectedError( $this, "DB connection was already closed." );
03577         }
03578 
03579         $this->runOnTransactionPreCommitCallbacks();
03580         $this->doCommit( $fname );
03581         if ( $this->mTrxDoneWrites ) {
03582             Profiler::instance()->transactionWritingOut(
03583                 $this->mServer, $this->mDBname, $this->mTrxShortId );
03584         }
03585         $this->runOnTransactionIdleCallbacks();
03586     }
03587 
03594     protected function doCommit( $fname ) {
03595         if ( $this->mTrxLevel ) {
03596             $this->query( 'COMMIT', $fname );
03597             $this->mTrxLevel = 0;
03598         }
03599     }
03600 
03614     final public function rollback( $fname = __METHOD__, $flush = '' ) {
03615         if ( $flush !== 'flush' ) {
03616             if ( !$this->mTrxLevel ) {
03617                 wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
03618                 return; // nothing to do
03619             } elseif ( $this->mTrxAutomatic ) {
03620                 wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" );
03621             }
03622         } else {
03623             if ( !$this->mTrxLevel ) {
03624                 return; // nothing to do
03625             } elseif ( !$this->mTrxAutomatic ) {
03626                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03627             }
03628         }
03629 
03630         # Avoid fatals if close() was called
03631         if ( !$this->isOpen() ) {
03632             throw new DBUnexpectedError( $this, "DB connection was already closed." );
03633         }
03634 
03635         $this->doRollback( $fname );
03636         $this->mTrxIdleCallbacks = array(); // cancel
03637         $this->mTrxPreCommitCallbacks = array(); // cancel
03638         $this->mTrxAtomicLevels = new SplStack;
03639         if ( $this->mTrxDoneWrites ) {
03640             Profiler::instance()->transactionWritingOut(
03641                 $this->mServer, $this->mDBname, $this->mTrxShortId );
03642         }
03643     }
03644 
03651     protected function doRollback( $fname ) {
03652         if ( $this->mTrxLevel ) {
03653             $this->query( 'ROLLBACK', $fname, true );
03654             $this->mTrxLevel = 0;
03655         }
03656     }
03657 
03673     public function duplicateTableStructure( $oldName, $newName, $temporary = false,
03674         $fname = __METHOD__
03675     ) {
03676         throw new MWException(
03677             'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
03678     }
03679 
03687     function listTables( $prefix = null, $fname = __METHOD__ ) {
03688         throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
03689     }
03690 
03695     final public function clearViewsCache() {
03696         $this->allViews = null;
03697     }
03698 
03710     public function listViews( $prefix = null, $fname = __METHOD__ ) {
03711         throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
03712     }
03713 
03721     public function isView( $name ) {
03722         throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
03723     }
03724 
03736     public function timestamp( $ts = 0 ) {
03737         return wfTimestamp( TS_MW, $ts );
03738     }
03739 
03753     public function timestampOrNull( $ts = null ) {
03754         if ( is_null( $ts ) ) {
03755             return null;
03756         } else {
03757             return $this->timestamp( $ts );
03758         }
03759     }
03760 
03775     public function resultObject( $result ) {
03776         if ( empty( $result ) ) {
03777             return false;
03778         } elseif ( $result instanceof ResultWrapper ) {
03779             return $result;
03780         } elseif ( $result === true ) {
03781             // Successful write query
03782             return $result;
03783         } else {
03784             return new ResultWrapper( $this, $result );
03785         }
03786     }
03787 
03793     public function ping() {
03794         # Stub. Not essential to override.
03795         return true;
03796     }
03797 
03807     public function getLag() {
03808         return 0;
03809     }
03810 
03816     function maxListLen() {
03817         return 0;
03818     }
03819 
03829     public function encodeBlob( $b ) {
03830         return $b;
03831     }
03832 
03841     public function decodeBlob( $b ) {
03842         return $b;
03843     }
03844 
03855     public function setSessionOptions( array $options ) {
03856     }
03857 
03874     public function sourceFile(
03875         $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
03876     ) {
03877         wfSuppressWarnings();
03878         $fp = fopen( $filename, 'r' );
03879         wfRestoreWarnings();
03880 
03881         if ( false === $fp ) {
03882             throw new MWException( "Could not open \"{$filename}\".\n" );
03883         }
03884 
03885         if ( !$fname ) {
03886             $fname = __METHOD__ . "( $filename )";
03887         }
03888 
03889         try {
03890             $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
03891         } catch ( MWException $e ) {
03892             fclose( $fp );
03893             throw $e;
03894         }
03895 
03896         fclose( $fp );
03897 
03898         return $error;
03899     }
03900 
03909     public function patchPath( $patch ) {
03910         global $IP;
03911 
03912         $dbType = $this->getType();
03913         if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03914             return "$IP/maintenance/$dbType/archives/$patch";
03915         } else {
03916             return "$IP/maintenance/archives/$patch";
03917         }
03918     }
03919 
03927     public function setSchemaVars( $vars ) {
03928         $this->mSchemaVars = $vars;
03929     }
03930 
03944     public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03945         $fname = __METHOD__, $inputCallback = false
03946     ) {
03947         $cmd = '';
03948 
03949         while ( !feof( $fp ) ) {
03950             if ( $lineCallback ) {
03951                 call_user_func( $lineCallback );
03952             }
03953 
03954             $line = trim( fgets( $fp ) );
03955 
03956             if ( $line == '' ) {
03957                 continue;
03958             }
03959 
03960             if ( '-' == $line[0] && '-' == $line[1] ) {
03961                 continue;
03962             }
03963 
03964             if ( $cmd != '' ) {
03965                 $cmd .= ' ';
03966             }
03967 
03968             $done = $this->streamStatementEnd( $cmd, $line );
03969 
03970             $cmd .= "$line\n";
03971 
03972             if ( $done || feof( $fp ) ) {
03973                 $cmd = $this->replaceVars( $cmd );
03974 
03975                 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
03976                     $res = $this->query( $cmd, $fname );
03977 
03978                     if ( $resultCallback ) {
03979                         call_user_func( $resultCallback, $res, $this );
03980                     }
03981 
03982                     if ( false === $res ) {
03983                         $err = $this->lastError();
03984 
03985                         return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03986                     }
03987                 }
03988                 $cmd = '';
03989             }
03990         }
03991 
03992         return true;
03993     }
03994 
04002     public function streamStatementEnd( &$sql, &$newLine ) {
04003         if ( $this->delimiter ) {
04004             $prev = $newLine;
04005             $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
04006             if ( $newLine != $prev ) {
04007                 return true;
04008             }
04009         }
04010 
04011         return false;
04012     }
04013 
04031     protected function replaceSchemaVars( $ins ) {
04032         $vars = $this->getSchemaVars();
04033         foreach ( $vars as $var => $value ) {
04034             // replace '{$var}'
04035             $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
04036             // replace `{$var}`
04037             $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
04038             // replace /*$var*/
04039             $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
04040         }
04041 
04042         return $ins;
04043     }
04044 
04051     protected function replaceVars( $ins ) {
04052         $ins = $this->replaceSchemaVars( $ins );
04053 
04054         // Table prefixes
04055         $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
04056             array( $this, 'tableNameCallback' ), $ins );
04057 
04058         // Index names
04059         $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
04060             array( $this, 'indexNameCallback' ), $ins );
04061 
04062         return $ins;
04063     }
04064 
04071     protected function getSchemaVars() {
04072         if ( $this->mSchemaVars ) {
04073             return $this->mSchemaVars;
04074         } else {
04075             return $this->getDefaultSchemaVars();
04076         }
04077     }
04078 
04087     protected function getDefaultSchemaVars() {
04088         return array();
04089     }
04090 
04097     protected function tableNameCallback( $matches ) {
04098         return $this->tableName( $matches[1] );
04099     }
04100 
04107     protected function indexNameCallback( $matches ) {
04108         return $this->indexName( $matches[1] );
04109     }
04110 
04119     public function lockIsFree( $lockName, $method ) {
04120         return true;
04121     }
04122 
04134     public function lock( $lockName, $method, $timeout = 5 ) {
04135         return true;
04136     }
04137 
04148     public function unlock( $lockName, $method ) {
04149         return true;
04150     }
04151 
04161     public function lockTables( $read, $write, $method, $lowPriority = true ) {
04162         return true;
04163     }
04164 
04171     public function unlockTables( $method ) {
04172         return true;
04173     }
04174 
04182     public function dropTable( $tableName, $fName = __METHOD__ ) {
04183         if ( !$this->tableExists( $tableName, $fName ) ) {
04184             return false;
04185         }
04186         $sql = "DROP TABLE " . $this->tableName( $tableName );
04187         if ( $this->cascadingDeletes() ) {
04188             $sql .= " CASCADE";
04189         }
04190 
04191         return $this->query( $sql, $fName );
04192     }
04193 
04200     public function getSearchEngine() {
04201         return 'SearchEngineDummy';
04202     }
04203 
04211     public function getInfinity() {
04212         return 'infinity';
04213     }
04214 
04221     public function encodeExpiry( $expiry ) {
04222         return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
04223             ? $this->getInfinity()
04224             : $this->timestamp( $expiry );
04225     }
04226 
04234     public function decodeExpiry( $expiry, $format = TS_MW ) {
04235         return ( $expiry == '' || $expiry == $this->getInfinity() )
04236             ? 'infinity'
04237             : wfTimestamp( $format, $expiry );
04238     }
04239 
04249     public function setBigSelects( $value = true ) {
04250         // no-op
04251     }
04252 
04257     public function __toString() {
04258         return (string)$this->mConn;
04259     }
04260 
04264     public function __destruct() {
04265         if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
04266             trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
04267         }
04268         if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
04269             $callers = array();
04270             foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
04271                 $callers[] = $callbackInfo[1];
04272             }
04273             $callers = implode( ', ', $callers );
04274             trigger_error( "DB transaction callbacks still pending (from $callers)." );
04275         }
04276     }
04277 }