MediaWiki  REL1_23
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 
00278     private $mTrxFname = null;
00279 
00286     private $mTrxDoneWrites = false;
00287 
00294     private $mTrxAutomatic = false;
00295 
00301     private $mTrxAtomicLevels;
00302 
00308     private $mTrxAutomaticAtomic = false;
00309 
00314     protected $fileHandle = null;
00315 
00320     protected $allViews = null;
00321 
00322 # ------------------------------------------------------------------------------
00323 # Accessors
00324 # ------------------------------------------------------------------------------
00325     # These optionally set a variable and return the previous state
00326 
00334     public function getServerInfo() {
00335         return $this->getServerVersion();
00336     }
00337 
00341     public function getDelimiter() {
00342         return $this->delimiter;
00343     }
00344 
00354     public function debug( $debug = null ) {
00355         return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00356     }
00357 
00379     public function bufferResults( $buffer = null ) {
00380         if ( is_null( $buffer ) ) {
00381             return !(bool)( $this->mFlags & DBO_NOBUFFER );
00382         } else {
00383             return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00384         }
00385     }
00386 
00399     public function ignoreErrors( $ignoreErrors = null ) {
00400         return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00401     }
00402 
00411     public function trxLevel() {
00412         return $this->mTrxLevel;
00413     }
00414 
00420     public function errorCount( $count = null ) {
00421         return wfSetVar( $this->mErrorCount, $count );
00422     }
00423 
00429     public function tablePrefix( $prefix = null ) {
00430         return wfSetVar( $this->mTablePrefix, $prefix );
00431     }
00432 
00438     public function dbSchema( $schema = null ) {
00439         return wfSetVar( $this->mSchema, $schema );
00440     }
00441 
00447     public function setFileHandle( $fh ) {
00448         $this->fileHandle = $fh;
00449     }
00450 
00460     public function getLBInfo( $name = null ) {
00461         if ( is_null( $name ) ) {
00462             return $this->mLBInfo;
00463         } else {
00464             if ( array_key_exists( $name, $this->mLBInfo ) ) {
00465                 return $this->mLBInfo[$name];
00466             } else {
00467                 return null;
00468             }
00469         }
00470     }
00471 
00480     public function setLBInfo( $name, $value = null ) {
00481         if ( is_null( $value ) ) {
00482             $this->mLBInfo = $name;
00483         } else {
00484             $this->mLBInfo[$name] = $value;
00485         }
00486     }
00487 
00495     public function setFakeSlaveLag( $lag ) {
00496     }
00497 
00503     public function setFakeMaster( $enabled = true ) {
00504     }
00505 
00511     public function cascadingDeletes() {
00512         return false;
00513     }
00514 
00520     public function cleanupTriggers() {
00521         return false;
00522     }
00523 
00530     public function strictIPs() {
00531         return false;
00532     }
00533 
00539     public function realTimestamps() {
00540         return false;
00541     }
00542 
00548     public function implicitGroupby() {
00549         return true;
00550     }
00551 
00558     public function implicitOrderby() {
00559         return true;
00560     }
00561 
00568     public function searchableIPs() {
00569         return false;
00570     }
00571 
00577     public function functionalIndexes() {
00578         return false;
00579     }
00580 
00585     public function lastQuery() {
00586         return $this->mLastQuery;
00587     }
00588 
00595     public function doneWrites() {
00596         return $this->mDoneWrites;
00597     }
00598 
00605     public function writesOrCallbacksPending() {
00606         return $this->mTrxLevel && (
00607             $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
00608         );
00609     }
00610 
00615     public function isOpen() {
00616         return $this->mOpened;
00617     }
00618 
00630     public function setFlag( $flag ) {
00631         global $wgDebugDBTransactions;
00632         $this->mFlags |= $flag;
00633         if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00634             wfDebug( "Implicit transactions are now enabled.\n" );
00635         }
00636     }
00637 
00649     public function clearFlag( $flag ) {
00650         global $wgDebugDBTransactions;
00651         $this->mFlags &= ~$flag;
00652         if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00653             wfDebug( "Implicit transactions are now disabled.\n" );
00654         }
00655     }
00656 
00669     public function getFlag( $flag ) {
00670         return !!( $this->mFlags & $flag );
00671     }
00672 
00679     public function getProperty( $name ) {
00680         return $this->$name;
00681     }
00682 
00686     public function getWikiID() {
00687         if ( $this->mTablePrefix ) {
00688             return "{$this->mDBname}-{$this->mTablePrefix}";
00689         } else {
00690             return $this->mDBname;
00691         }
00692     }
00693 
00699     public function getSchemaPath() {
00700         global $IP;
00701         if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
00702             return "$IP/maintenance/" . $this->getType() . "/tables.sql";
00703         } else {
00704             return "$IP/maintenance/tables.sql";
00705         }
00706     }
00707 
00708 # ------------------------------------------------------------------------------
00709 # Other functions
00710 # ------------------------------------------------------------------------------
00711 
00724     function __construct( $params = null ) {
00725         global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
00726 
00727         $this->mTrxAtomicLevels = new SplStack;
00728 
00729         if ( is_array( $params ) ) { // MW 1.22
00730             $server = $params['host'];
00731             $user = $params['user'];
00732             $password = $params['password'];
00733             $dbName = $params['dbname'];
00734             $flags = $params['flags'];
00735             $tablePrefix = $params['tablePrefix'];
00736             $schema = $params['schema'];
00737             $foreign = $params['foreign'];
00738         } else { // legacy calling pattern
00739             wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" );
00740             $args = func_get_args();
00741             $server = isset( $args[0] ) ? $args[0] : false;
00742             $user = isset( $args[1] ) ? $args[1] : false;
00743             $password = isset( $args[2] ) ? $args[2] : false;
00744             $dbName = isset( $args[3] ) ? $args[3] : false;
00745             $flags = isset( $args[4] ) ? $args[4] : 0;
00746             $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global';
00747             $schema = 'get from global';
00748             $foreign = isset( $args[6] ) ? $args[6] : false;
00749         }
00750 
00751         $this->mFlags = $flags;
00752         if ( $this->mFlags & DBO_DEFAULT ) {
00753             if ( $wgCommandLineMode ) {
00754                 $this->mFlags &= ~DBO_TRX;
00755                 if ( $wgDebugDBTransactions ) {
00756                     wfDebug( "Implicit transaction open disabled.\n" );
00757                 }
00758             } else {
00759                 $this->mFlags |= DBO_TRX;
00760                 if ( $wgDebugDBTransactions ) {
00761                     wfDebug( "Implicit transaction open enabled.\n" );
00762                 }
00763             }
00764         }
00765 
00767         if ( $tablePrefix == 'get from global' ) {
00768             $this->mTablePrefix = $wgDBprefix;
00769         } else {
00770             $this->mTablePrefix = $tablePrefix;
00771         }
00772 
00774         if ( $schema == 'get from global' ) {
00775             $this->mSchema = $wgDBmwschema;
00776         } else {
00777             $this->mSchema = $schema;
00778         }
00779 
00780         $this->mForeign = $foreign;
00781 
00782         if ( $user ) {
00783             $this->open( $server, $user, $password, $dbName );
00784         }
00785     }
00786 
00792     public function __sleep() {
00793         throw new MWException( 'Database serialization may cause problems, since ' .
00794             'the connection is not restored on wakeup.' );
00795     }
00796 
00819     final public static function factory( $dbType, $p = array() ) {
00820         $canonicalDBTypes = array(
00821             'mysql' => array( 'mysqli', 'mysql' ),
00822             'postgres' => array(),
00823             'sqlite' => array(),
00824             'oracle' => array(),
00825             'mssql' => array(),
00826         );
00827 
00828         $driver = false;
00829         $dbType = strtolower( $dbType );
00830         if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
00831             $possibleDrivers = $canonicalDBTypes[$dbType];
00832             if ( !empty( $p['driver'] ) ) {
00833                 if ( in_array( $p['driver'], $possibleDrivers ) ) {
00834                     $driver = $p['driver'];
00835                 } else {
00836                     throw new MWException( __METHOD__ .
00837                         " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
00838                 }
00839             } else {
00840                 foreach ( $possibleDrivers as $posDriver ) {
00841                     if ( extension_loaded( $posDriver ) ) {
00842                         $driver = $posDriver;
00843                         break;
00844                     }
00845                 }
00846             }
00847         } else {
00848             $driver = $dbType;
00849         }
00850         if ( $driver === false ) {
00851             throw new MWException( __METHOD__ .
00852                 " no viable database extension found for type '$dbType'" );
00853         }
00854 
00855         // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
00856         // and everything else doesn't use a schema (e.g. null)
00857         // Although postgres and oracle support schemas, we don't use them (yet)
00858         // to maintain backwards compatibility
00859         $defaultSchemas = array(
00860             'mysql' => null,
00861             'postgres' => null,
00862             'sqlite' => null,
00863             'oracle' => null,
00864             'mssql' => 'get from global',
00865         );
00866 
00867         $class = 'Database' . ucfirst( $driver );
00868         if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
00869             $params = array(
00870                 'host' => isset( $p['host'] ) ? $p['host'] : false,
00871                 'user' => isset( $p['user'] ) ? $p['user'] : false,
00872                 'password' => isset( $p['password'] ) ? $p['password'] : false,
00873                 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false,
00874                 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0,
00875                 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
00876                 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType],
00877                 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false
00878             );
00879 
00880             return new $class( $params );
00881         } else {
00882             return null;
00883         }
00884     }
00885 
00886     protected function installErrorHandler() {
00887         $this->mPHPError = false;
00888         $this->htmlErrors = ini_set( 'html_errors', '0' );
00889         set_error_handler( array( $this, 'connectionErrorHandler' ) );
00890     }
00891 
00895     protected function restoreErrorHandler() {
00896         restore_error_handler();
00897         if ( $this->htmlErrors !== false ) {
00898             ini_set( 'html_errors', $this->htmlErrors );
00899         }
00900         if ( $this->mPHPError ) {
00901             $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00902             $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
00903 
00904             return $error;
00905         } else {
00906             return false;
00907         }
00908     }
00909 
00914     public function connectionErrorHandler( $errno, $errstr ) {
00915         $this->mPHPError = $errstr;
00916     }
00917 
00925     public function close() {
00926         if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
00927             throw new MWException( "Transaction idle callbacks still pending." );
00928         }
00929         $this->mOpened = false;
00930         if ( $this->mConn ) {
00931             if ( $this->trxLevel() ) {
00932                 if ( !$this->mTrxAutomatic ) {
00933                     wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
00934                         " performing implicit commit before closing connection!" );
00935                 }
00936 
00937                 $this->commit( __METHOD__, 'flush' );
00938             }
00939 
00940             $ret = $this->closeConnection();
00941             $this->mConn = false;
00942 
00943             return $ret;
00944         } else {
00945             return true;
00946         }
00947     }
00948 
00954     abstract protected function closeConnection();
00955 
00960     function reportConnectionError( $error = 'Unknown error' ) {
00961         $myError = $this->lastError();
00962         if ( $myError ) {
00963             $error = $myError;
00964         }
00965 
00966         # New method
00967         throw new DBConnectionError( $this, $error );
00968     }
00969 
00977     abstract protected function doQuery( $sql );
00978 
00986     public function isWriteQuery( $sql ) {
00987         return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
00988     }
00989 
01012     public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
01013         global $wgUser, $wgDebugDBTransactions;
01014 
01015         $this->mLastQuery = $sql;
01016         if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
01017             # Set a flag indicating that writes have been done
01018             wfDebug( __METHOD__ . ": Writes done: $sql\n" );
01019             $this->mDoneWrites = true;
01020         }
01021 
01022         # Add a comment for easy SHOW PROCESSLIST interpretation
01023         if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
01024             $userName = $wgUser->getName();
01025             if ( mb_strlen( $userName ) > 15 ) {
01026                 $userName = mb_substr( $userName, 0, 15 ) . '...';
01027             }
01028             $userName = str_replace( '/', '', $userName );
01029         } else {
01030             $userName = '';
01031         }
01032 
01033         // Add trace comment to the begin of the sql string, right after the operator.
01034         // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
01035         $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
01036 
01037         # If DBO_TRX is set, start a transaction
01038         if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
01039             $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
01040         ) {
01041             # Avoid establishing transactions for SHOW and SET statements too -
01042             # that would delay transaction initializations to once connection
01043             # is really used by application
01044             $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
01045             if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
01046                 if ( $wgDebugDBTransactions ) {
01047                     wfDebug( "Implicit transaction start.\n" );
01048                 }
01049                 $this->begin( __METHOD__ . " ($fname)" );
01050                 $this->mTrxAutomatic = true;
01051             }
01052         }
01053 
01054         # Keep track of whether the transaction has write queries pending
01055         if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
01056             $this->mTrxDoneWrites = true;
01057             Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname );
01058         }
01059 
01060         $queryProf = '';
01061         $totalProf = '';
01062         $isMaster = !is_null( $this->getLBInfo( 'master' ) );
01063 
01064         if ( !Profiler::instance()->isStub() ) {
01065             # generalizeSQL will probably cut down the query to reasonable
01066             # logging size most of the time. The substr is really just a sanity check.
01067             if ( $isMaster ) {
01068                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
01069                 $totalProf = 'DatabaseBase::query-master';
01070             } else {
01071                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
01072                 $totalProf = 'DatabaseBase::query';
01073             }
01074             wfProfileIn( $totalProf );
01075             wfProfileIn( $queryProf );
01076         }
01077 
01078         if ( $this->debug() ) {
01079             static $cnt = 0;
01080 
01081             $cnt++;
01082             $sqlx = substr( $commentedSql, 0, 500 );
01083             $sqlx = strtr( $sqlx, "\t\n", '  ' );
01084 
01085             $master = $isMaster ? 'master' : 'slave';
01086             wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
01087         }
01088 
01089         $queryId = MWDebug::query( $sql, $fname, $isMaster );
01090 
01091         # Do the query and handle errors
01092         $ret = $this->doQuery( $commentedSql );
01093 
01094         MWDebug::queryTime( $queryId );
01095 
01096         # Try reconnecting if the connection was lost
01097         if ( false === $ret && $this->wasErrorReissuable() ) {
01098             # Transaction is gone, like it or not
01099             $hadTrx = $this->mTrxLevel; // possible lost transaction
01100             $this->mTrxLevel = 0;
01101             wfDebug( "Connection lost, reconnecting...\n" );
01102 
01103             if ( $this->ping() ) {
01104                 wfDebug( "Reconnected\n" );
01105                 $sqlx = substr( $commentedSql, 0, 500 );
01106                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
01107                 global $wgRequestTime;
01108                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
01109                 if ( $elapsed < 300 ) {
01110                     # Not a database error to lose a transaction after a minute or two
01111                     wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" );
01112                 }
01113                 if ( !$hadTrx ) {
01114                     # Should be safe to silently retry
01115                     $ret = $this->doQuery( $commentedSql );
01116                 }
01117             } else {
01118                 wfDebug( "Failed\n" );
01119             }
01120         }
01121 
01122         if ( false === $ret ) {
01123             $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
01124         }
01125 
01126         if ( !Profiler::instance()->isStub() ) {
01127             wfProfileOut( $queryProf );
01128             wfProfileOut( $totalProf );
01129         }
01130 
01131         return $this->resultObject( $ret );
01132     }
01133 
01145     public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
01146         # Ignore errors during error handling to avoid infinite recursion
01147         $ignore = $this->ignoreErrors( true );
01148         ++$this->mErrorCount;
01149 
01150         if ( $ignore || $tempIgnore ) {
01151             wfDebug( "SQL ERROR (ignored): $error\n" );
01152             $this->ignoreErrors( $ignore );
01153         } else {
01154             $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
01155             wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
01156             wfDebug( "SQL ERROR: " . $error . "\n" );
01157             throw new DBQueryError( $this, $error, $errno, $sql, $fname );
01158         }
01159     }
01160 
01175     protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
01176         /* MySQL doesn't support prepared statements (yet), so just
01177          * pack up the query for reference. We'll manually replace
01178          * the bits later.
01179          */
01180         return array( 'query' => $sql, 'func' => $func );
01181     }
01182 
01187     protected function freePrepared( $prepared ) {
01188         /* No-op by default */
01189     }
01190 
01198     public function execute( $prepared, $args = null ) {
01199         if ( !is_array( $args ) ) {
01200             # Pull the var args
01201             $args = func_get_args();
01202             array_shift( $args );
01203         }
01204 
01205         $sql = $this->fillPrepared( $prepared['query'], $args );
01206 
01207         return $this->query( $sql, $prepared['func'] );
01208     }
01209 
01217     public function fillPrepared( $preparedQuery, $args ) {
01218         reset( $args );
01219         $this->preparedArgs =& $args;
01220 
01221         return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01222             array( &$this, 'fillPreparedArg' ), $preparedQuery );
01223     }
01224 
01234     protected function fillPreparedArg( $matches ) {
01235         switch ( $matches[1] ) {
01236             case '\\?':
01237                 return '?';
01238             case '\\!':
01239                 return '!';
01240             case '\\&':
01241                 return '&';
01242         }
01243 
01244         list( /* $n */, $arg ) = each( $this->preparedArgs );
01245 
01246         switch ( $matches[1] ) {
01247             case '?':
01248                 return $this->addQuotes( $arg );
01249             case '!':
01250                 return $arg;
01251             case '&':
01252                 # return $this->addQuotes( file_get_contents( $arg ) );
01253                 throw new DBUnexpectedError(
01254                     $this,
01255                     '& mode is not implemented. If it\'s really needed, uncomment the line above.'
01256                 );
01257             default:
01258                 throw new DBUnexpectedError(
01259                     $this,
01260                     'Received invalid match. This should never happen!'
01261                 );
01262         }
01263     }
01264 
01272     public function freeResult( $res ) {
01273     }
01274 
01292     public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
01293         $options = array()
01294     ) {
01295         if ( !is_array( $options ) ) {
01296             $options = array( $options );
01297         }
01298 
01299         $options['LIMIT'] = 1;
01300 
01301         $res = $this->select( $table, $var, $cond, $fname, $options );
01302 
01303         if ( $res === false || !$this->numRows( $res ) ) {
01304             return false;
01305         }
01306 
01307         $row = $this->fetchRow( $res );
01308 
01309         if ( $row !== false ) {
01310             return reset( $row );
01311         } else {
01312             return false;
01313         }
01314     }
01315 
01325     public function makeSelectOptions( $options ) {
01326         $preLimitTail = $postLimitTail = '';
01327         $startOpts = '';
01328 
01329         $noKeyOptions = array();
01330 
01331         foreach ( $options as $key => $option ) {
01332             if ( is_numeric( $key ) ) {
01333                 $noKeyOptions[$option] = true;
01334             }
01335         }
01336 
01337         $preLimitTail .= $this->makeGroupByWithHaving( $options );
01338 
01339         $preLimitTail .= $this->makeOrderBy( $options );
01340 
01341         // if (isset($options['LIMIT'])) {
01342         //  $tailOpts .= $this->limitResult('', $options['LIMIT'],
01343         //      isset($options['OFFSET']) ? $options['OFFSET']
01344         //      : false);
01345         // }
01346 
01347         if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01348             $postLimitTail .= ' FOR UPDATE';
01349         }
01350 
01351         if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01352             $postLimitTail .= ' LOCK IN SHARE MODE';
01353         }
01354 
01355         if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01356             $startOpts .= 'DISTINCT';
01357         }
01358 
01359         # Various MySQL extensions
01360         if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01361             $startOpts .= ' /*! STRAIGHT_JOIN */';
01362         }
01363 
01364         if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01365             $startOpts .= ' HIGH_PRIORITY';
01366         }
01367 
01368         if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01369             $startOpts .= ' SQL_BIG_RESULT';
01370         }
01371 
01372         if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01373             $startOpts .= ' SQL_BUFFER_RESULT';
01374         }
01375 
01376         if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01377             $startOpts .= ' SQL_SMALL_RESULT';
01378         }
01379 
01380         if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01381             $startOpts .= ' SQL_CALC_FOUND_ROWS';
01382         }
01383 
01384         if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01385             $startOpts .= ' SQL_CACHE';
01386         }
01387 
01388         if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01389             $startOpts .= ' SQL_NO_CACHE';
01390         }
01391 
01392         if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
01393             $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01394         } else {
01395             $useIndex = '';
01396         }
01397 
01398         return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01399     }
01400 
01409     public function makeGroupByWithHaving( $options ) {
01410         $sql = '';
01411         if ( isset( $options['GROUP BY'] ) ) {
01412             $gb = is_array( $options['GROUP BY'] )
01413                 ? implode( ',', $options['GROUP BY'] )
01414                 : $options['GROUP BY'];
01415             $sql .= ' GROUP BY ' . $gb;
01416         }
01417         if ( isset( $options['HAVING'] ) ) {
01418             $having = is_array( $options['HAVING'] )
01419                 ? $this->makeList( $options['HAVING'], LIST_AND )
01420                 : $options['HAVING'];
01421             $sql .= ' HAVING ' . $having;
01422         }
01423 
01424         return $sql;
01425     }
01426 
01435     public function makeOrderBy( $options ) {
01436         if ( isset( $options['ORDER BY'] ) ) {
01437             $ob = is_array( $options['ORDER BY'] )
01438                 ? implode( ',', $options['ORDER BY'] )
01439                 : $options['ORDER BY'];
01440 
01441             return ' ORDER BY ' . $ob;
01442         }
01443 
01444         return '';
01445     }
01446 
01586     public function select( $table, $vars, $conds = '', $fname = __METHOD__,
01587         $options = array(), $join_conds = array() ) {
01588         $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01589 
01590         return $this->query( $sql, $fname );
01591     }
01592 
01609     public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
01610         $options = array(), $join_conds = array()
01611     ) {
01612         if ( is_array( $vars ) ) {
01613             $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
01614         }
01615 
01616         $options = (array)$options;
01617         $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01618             ? $options['USE INDEX']
01619             : array();
01620 
01621         if ( is_array( $table ) ) {
01622             $from = ' FROM ' .
01623                 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
01624         } elseif ( $table != '' ) {
01625             if ( $table[0] == ' ' ) {
01626                 $from = ' FROM ' . $table;
01627             } else {
01628                 $from = ' FROM ' .
01629                     $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() );
01630             }
01631         } else {
01632             $from = '';
01633         }
01634 
01635         list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
01636             $this->makeSelectOptions( $options );
01637 
01638         if ( !empty( $conds ) ) {
01639             if ( is_array( $conds ) ) {
01640                 $conds = $this->makeList( $conds, LIST_AND );
01641             }
01642             $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01643         } else {
01644             $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01645         }
01646 
01647         if ( isset( $options['LIMIT'] ) ) {
01648             $sql = $this->limitResult( $sql, $options['LIMIT'],
01649                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01650         }
01651         $sql = "$sql $postLimitTail";
01652 
01653         if ( isset( $options['EXPLAIN'] ) ) {
01654             $sql = 'EXPLAIN ' . $sql;
01655         }
01656 
01657         return $sql;
01658     }
01659 
01674     public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
01675         $options = array(), $join_conds = array()
01676     ) {
01677         $options = (array)$options;
01678         $options['LIMIT'] = 1;
01679         $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01680 
01681         if ( $res === false ) {
01682             return false;
01683         }
01684 
01685         if ( !$this->numRows( $res ) ) {
01686             return false;
01687         }
01688 
01689         $obj = $this->fetchObject( $res );
01690 
01691         return $obj;
01692     }
01693 
01714     public function estimateRowCount( $table, $vars = '*', $conds = '',
01715         $fname = __METHOD__, $options = array()
01716     ) {
01717         $rows = 0;
01718         $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
01719 
01720         if ( $res ) {
01721             $row = $this->fetchRow( $res );
01722             $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01723         }
01724 
01725         return $rows;
01726     }
01727 
01736     static function generalizeSQL( $sql ) {
01737         # This does the same as the regexp below would do, but in such a way
01738         # as to avoid crashing php on some large strings.
01739         # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
01740 
01741         $sql = str_replace( "\\\\", '', $sql );
01742         $sql = str_replace( "\\'", '', $sql );
01743         $sql = str_replace( "\\\"", '', $sql );
01744         $sql = preg_replace( "/'.*'/s", "'X'", $sql );
01745         $sql = preg_replace( '/".*"/s', "'X'", $sql );
01746 
01747         # All newlines, tabs, etc replaced by single space
01748         $sql = preg_replace( '/\s+/', ' ', $sql );
01749 
01750         # All numbers => N
01751         $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
01752         $sql = preg_replace( '/-?\d+/s', 'N', $sql );
01753 
01754         return $sql;
01755     }
01756 
01765     public function fieldExists( $table, $field, $fname = __METHOD__ ) {
01766         $info = $this->fieldInfo( $table, $field );
01767 
01768         return (bool)$info;
01769     }
01770 
01781     public function indexExists( $table, $index, $fname = __METHOD__ ) {
01782         if ( !$this->tableExists( $table ) ) {
01783             return null;
01784         }
01785 
01786         $info = $this->indexInfo( $table, $index, $fname );
01787         if ( is_null( $info ) ) {
01788             return null;
01789         } else {
01790             return $info !== false;
01791         }
01792     }
01793 
01801     public function tableExists( $table, $fname = __METHOD__ ) {
01802         $table = $this->tableName( $table );
01803         $old = $this->ignoreErrors( true );
01804         $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01805         $this->ignoreErrors( $old );
01806 
01807         return (bool)$res;
01808     }
01809 
01818     public function indexUnique( $table, $index ) {
01819         $indexInfo = $this->indexInfo( $table, $index );
01820 
01821         if ( !$indexInfo ) {
01822             return null;
01823         }
01824 
01825         return !$indexInfo[0]->Non_unique;
01826     }
01827 
01834     protected function makeInsertOptions( $options ) {
01835         return implode( ' ', $options );
01836     }
01837 
01871     public function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
01872         # No rows to insert, easy just return now
01873         if ( !count( $a ) ) {
01874             return true;
01875         }
01876 
01877         $table = $this->tableName( $table );
01878 
01879         if ( !is_array( $options ) ) {
01880             $options = array( $options );
01881         }
01882 
01883         $fh = null;
01884         if ( isset( $options['fileHandle'] ) ) {
01885             $fh = $options['fileHandle'];
01886         }
01887         $options = $this->makeInsertOptions( $options );
01888 
01889         if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01890             $multi = true;
01891             $keys = array_keys( $a[0] );
01892         } else {
01893             $multi = false;
01894             $keys = array_keys( $a );
01895         }
01896 
01897         $sql = 'INSERT ' . $options .
01898             " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01899 
01900         if ( $multi ) {
01901             $first = true;
01902             foreach ( $a as $row ) {
01903                 if ( $first ) {
01904                     $first = false;
01905                 } else {
01906                     $sql .= ',';
01907                 }
01908                 $sql .= '(' . $this->makeList( $row ) . ')';
01909             }
01910         } else {
01911             $sql .= '(' . $this->makeList( $a ) . ')';
01912         }
01913 
01914         if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
01915             return false;
01916         } elseif ( $fh !== null ) {
01917             return true;
01918         }
01919 
01920         return (bool)$this->query( $sql, $fname );
01921     }
01922 
01929     protected function makeUpdateOptionsArray( $options ) {
01930         if ( !is_array( $options ) ) {
01931             $options = array( $options );
01932         }
01933 
01934         $opts = array();
01935 
01936         if ( in_array( 'LOW_PRIORITY', $options ) ) {
01937             $opts[] = $this->lowPriorityOption();
01938         }
01939 
01940         if ( in_array( 'IGNORE', $options ) ) {
01941             $opts[] = 'IGNORE';
01942         }
01943 
01944         return $opts;
01945     }
01946 
01953     protected function makeUpdateOptions( $options ) {
01954         $opts = $this->makeUpdateOptionsArray( $options );
01955 
01956         return implode( ' ', $opts );
01957     }
01958 
01977     function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
01978         $table = $this->tableName( $table );
01979         $opts = $this->makeUpdateOptions( $options );
01980         $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01981 
01982         if ( $conds !== array() && $conds !== '*' ) {
01983             $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01984         }
01985 
01986         return $this->query( $sql, $fname );
01987     }
01988 
02003     public function makeList( $a, $mode = LIST_COMMA ) {
02004         if ( !is_array( $a ) ) {
02005             throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
02006         }
02007 
02008         $first = true;
02009         $list = '';
02010 
02011         foreach ( $a as $field => $value ) {
02012             if ( !$first ) {
02013                 if ( $mode == LIST_AND ) {
02014                     $list .= ' AND ';
02015                 } elseif ( $mode == LIST_OR ) {
02016                     $list .= ' OR ';
02017                 } else {
02018                     $list .= ',';
02019                 }
02020             } else {
02021                 $first = false;
02022             }
02023 
02024             if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
02025                 $list .= "($value)";
02026             } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
02027                 $list .= "$value";
02028             } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
02029                 if ( count( $value ) == 0 ) {
02030                     throw new MWException( __METHOD__ . ": empty input for field $field" );
02031                 } elseif ( count( $value ) == 1 ) {
02032                     // Special-case single values, as IN isn't terribly efficient
02033                     // Don't necessarily assume the single key is 0; we don't
02034                     // enforce linear numeric ordering on other arrays here.
02035                     $value = array_values( $value );
02036                     $list .= $field . " = " . $this->addQuotes( $value[0] );
02037                 } else {
02038                     $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
02039                 }
02040             } elseif ( $value === null ) {
02041                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
02042                     $list .= "$field IS ";
02043                 } elseif ( $mode == LIST_SET ) {
02044                     $list .= "$field = ";
02045                 }
02046                 $list .= 'NULL';
02047             } else {
02048                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
02049                     $list .= "$field = ";
02050                 }
02051                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
02052             }
02053         }
02054 
02055         return $list;
02056     }
02057 
02068     public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
02069         $conds = array();
02070 
02071         foreach ( $data as $base => $sub ) {
02072             if ( count( $sub ) ) {
02073                 $conds[] = $this->makeList(
02074                     array( $baseKey => $base, $subKey => array_keys( $sub ) ),
02075                     LIST_AND );
02076             }
02077         }
02078 
02079         if ( $conds ) {
02080             return $this->makeList( $conds, LIST_OR );
02081         } else {
02082             // Nothing to search for...
02083             return false;
02084         }
02085     }
02086 
02095     public function aggregateValue( $valuedata, $valuename = 'value' ) {
02096         return $valuename;
02097     }
02098 
02103     public function bitNot( $field ) {
02104         return "(~$field)";
02105     }
02106 
02112     public function bitAnd( $fieldLeft, $fieldRight ) {
02113         return "($fieldLeft & $fieldRight)";
02114     }
02115 
02121     public function bitOr( $fieldLeft, $fieldRight ) {
02122         return "($fieldLeft | $fieldRight)";
02123     }
02124 
02131     public function buildConcat( $stringList ) {
02132         return 'CONCAT(' . implode( ',', $stringList ) . ')';
02133     }
02134 
02151     public function buildGroupConcatField(
02152         $delim, $table, $field, $conds = '', $join_conds = array()
02153     ) {
02154         $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
02155 
02156         return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')';
02157     }
02158 
02168     public function selectDB( $db ) {
02169         # Stub. Shouldn't cause serious problems if it's not overridden, but
02170         # if your database engine supports a concept similar to MySQL's
02171         # databases you may as well.
02172         $this->mDBname = $db;
02173 
02174         return true;
02175     }
02176 
02180     public function getDBname() {
02181         return $this->mDBname;
02182     }
02183 
02187     public function getServer() {
02188         return $this->mServer;
02189     }
02190 
02208     public function tableName( $name, $format = 'quoted' ) {
02209         global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema;
02210         # Skip the entire process when we have a string quoted on both ends.
02211         # Note that we check the end so that we will still quote any use of
02212         # use of `database`.table. But won't break things if someone wants
02213         # to query a database table with a dot in the name.
02214         if ( $this->isQuotedIdentifier( $name ) ) {
02215             return $name;
02216         }
02217 
02218         # Lets test for any bits of text that should never show up in a table
02219         # name. Basically anything like JOIN or ON which are actually part of
02220         # SQL queries, but may end up inside of the table value to combine
02221         # sql. Such as how the API is doing.
02222         # Note that we use a whitespace test rather than a \b test to avoid
02223         # any remote case where a word like on may be inside of a table name
02224         # surrounded by symbols which may be considered word breaks.
02225         if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
02226             return $name;
02227         }
02228 
02229         # Split database and table into proper variables.
02230         # We reverse the explode so that database.table and table both output
02231         # the correct table.
02232         $dbDetails = explode( '.', $name, 2 );
02233         if ( count( $dbDetails ) == 3 ) {
02234             list( $database, $schema, $table ) = $dbDetails;
02235             # We don't want any prefix added in this case
02236             $prefix = '';
02237         } elseif ( count( $dbDetails ) == 2 ) {
02238             list( $database, $table ) = $dbDetails;
02239             # We don't want any prefix added in this case
02240             # In dbs that support it, $database may actually be the schema
02241             # but that doesn't affect any of the functionality here
02242             $prefix = '';
02243             $schema = null;
02244         } else {
02245             list( $table ) = $dbDetails;
02246             if ( $wgSharedDB !== null # We have a shared database
02247                 && $this->mForeign == false # We're not working on a foreign database
02248                 && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
02249                 && in_array( $table, $wgSharedTables ) # A shared table is selected
02250             ) {
02251                 $database = $wgSharedDB;
02252                 $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
02253                 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
02254             } else {
02255                 $database = null;
02256                 $schema = $this->mSchema; # Default schema
02257                 $prefix = $this->mTablePrefix; # Default prefix
02258             }
02259         }
02260 
02261         # Quote $table and apply the prefix if not quoted.
02262         # $tableName might be empty if this is called from Database::replaceVars()
02263         $tableName = "{$prefix}{$table}";
02264         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
02265             $tableName = $this->addIdentifierQuotes( $tableName );
02266         }
02267 
02268         # Quote $schema and merge it with the table name if needed
02269         if ( $schema !== null ) {
02270             if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
02271                 $schema = $this->addIdentifierQuotes( $schema );
02272             }
02273             $tableName = $schema . '.' . $tableName;
02274         }
02275 
02276         # Quote $database and merge it with the table name if needed
02277         if ( $database !== null ) {
02278             if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
02279                 $database = $this->addIdentifierQuotes( $database );
02280             }
02281             $tableName = $database . '.' . $tableName;
02282         }
02283 
02284         return $tableName;
02285     }
02286 
02298     public function tableNames() {
02299         $inArray = func_get_args();
02300         $retVal = array();
02301 
02302         foreach ( $inArray as $name ) {
02303             $retVal[$name] = $this->tableName( $name );
02304         }
02305 
02306         return $retVal;
02307     }
02308 
02320     public function tableNamesN() {
02321         $inArray = func_get_args();
02322         $retVal = array();
02323 
02324         foreach ( $inArray as $name ) {
02325             $retVal[] = $this->tableName( $name );
02326         }
02327 
02328         return $retVal;
02329     }
02330 
02339     public function tableNameWithAlias( $name, $alias = false ) {
02340         if ( !$alias || $alias == $name ) {
02341             return $this->tableName( $name );
02342         } else {
02343             return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02344         }
02345     }
02346 
02353     public function tableNamesWithAlias( $tables ) {
02354         $retval = array();
02355         foreach ( $tables as $alias => $table ) {
02356             if ( is_numeric( $alias ) ) {
02357                 $alias = $table;
02358             }
02359             $retval[] = $this->tableNameWithAlias( $table, $alias );
02360         }
02361 
02362         return $retval;
02363     }
02364 
02373     public function fieldNameWithAlias( $name, $alias = false ) {
02374         if ( !$alias || (string)$alias === (string)$name ) {
02375             return $name;
02376         } else {
02377             return $name . ' AS ' . $alias; //PostgreSQL needs AS
02378         }
02379     }
02380 
02387     public function fieldNamesWithAlias( $fields ) {
02388         $retval = array();
02389         foreach ( $fields as $alias => $field ) {
02390             if ( is_numeric( $alias ) ) {
02391                 $alias = $field;
02392             }
02393             $retval[] = $this->fieldNameWithAlias( $field, $alias );
02394         }
02395 
02396         return $retval;
02397     }
02398 
02408     protected function tableNamesWithUseIndexOrJOIN(
02409         $tables, $use_index = array(), $join_conds = array()
02410     ) {
02411         $ret = array();
02412         $retJOIN = array();
02413         $use_index = (array)$use_index;
02414         $join_conds = (array)$join_conds;
02415 
02416         foreach ( $tables as $alias => $table ) {
02417             if ( !is_string( $alias ) ) {
02418                 // No alias? Set it equal to the table name
02419                 $alias = $table;
02420             }
02421             // Is there a JOIN clause for this table?
02422             if ( isset( $join_conds[$alias] ) ) {
02423                 list( $joinType, $conds ) = $join_conds[$alias];
02424                 $tableClause = $joinType;
02425                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02426                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02427                     $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02428                     if ( $use != '' ) {
02429                         $tableClause .= ' ' . $use;
02430                     }
02431                 }
02432                 $on = $this->makeList( (array)$conds, LIST_AND );
02433                 if ( $on != '' ) {
02434                     $tableClause .= ' ON (' . $on . ')';
02435                 }
02436 
02437                 $retJOIN[] = $tableClause;
02438             } elseif ( isset( $use_index[$alias] ) ) {
02439                 // Is there an INDEX clause for this table?
02440                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02441                 $tableClause .= ' ' . $this->useIndexClause(
02442                     implode( ',', (array)$use_index[$alias] )
02443                 );
02444 
02445                 $ret[] = $tableClause;
02446             } else {
02447                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02448 
02449                 $ret[] = $tableClause;
02450             }
02451         }
02452 
02453         // We can't separate explicit JOIN clauses with ',', use ' ' for those
02454         $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02455         $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02456 
02457         // Compile our final table clause
02458         return implode( ' ', array( $implicitJoins, $explicitJoins ) );
02459     }
02460 
02467     protected function indexName( $index ) {
02468         // Backwards-compatibility hack
02469         $renamed = array(
02470             'ar_usertext_timestamp' => 'usertext_timestamp',
02471             'un_user_id' => 'user_id',
02472             'un_user_ip' => 'user_ip',
02473         );
02474 
02475         if ( isset( $renamed[$index] ) ) {
02476             return $renamed[$index];
02477         } else {
02478             return $index;
02479         }
02480     }
02481 
02488     public function addQuotes( $s ) {
02489         if ( $s === null ) {
02490             return 'NULL';
02491         } else {
02492             # This will also quote numeric values. This should be harmless,
02493             # and protects against weird problems that occur when they really
02494             # _are_ strings such as article titles and string->number->string
02495             # conversion is not 1:1.
02496             return "'" . $this->strencode( $s ) . "'";
02497         }
02498     }
02499 
02509     public function addIdentifierQuotes( $s ) {
02510         return '"' . str_replace( '"', '""', $s ) . '"';
02511     }
02512 
02520     public function isQuotedIdentifier( $name ) {
02521         return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02522     }
02523 
02528     protected function escapeLikeInternal( $s ) {
02529         $s = str_replace( '\\', '\\\\', $s );
02530         $s = $this->strencode( $s );
02531         $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
02532 
02533         return $s;
02534     }
02535 
02552     public function buildLike() {
02553         $params = func_get_args();
02554 
02555         if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02556             $params = $params[0];
02557         }
02558 
02559         $s = '';
02560 
02561         foreach ( $params as $value ) {
02562             if ( $value instanceof LikeMatch ) {
02563                 $s .= $value->toString();
02564             } else {
02565                 $s .= $this->escapeLikeInternal( $value );
02566             }
02567         }
02568 
02569         return " LIKE '" . $s . "' ";
02570     }
02571 
02577     public function anyChar() {
02578         return new LikeMatch( '_' );
02579     }
02580 
02586     public function anyString() {
02587         return new LikeMatch( '%' );
02588     }
02589 
02601     public function nextSequenceValue( $seqName ) {
02602         return null;
02603     }
02604 
02615     public function useIndexClause( $index ) {
02616         return '';
02617     }
02618 
02641     public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
02642         $quotedTable = $this->tableName( $table );
02643 
02644         if ( count( $rows ) == 0 ) {
02645             return;
02646         }
02647 
02648         # Single row case
02649         if ( !is_array( reset( $rows ) ) ) {
02650             $rows = array( $rows );
02651         }
02652 
02653         foreach ( $rows as $row ) {
02654             # Delete rows which collide
02655             if ( $uniqueIndexes ) {
02656                 $sql = "DELETE FROM $quotedTable WHERE ";
02657                 $first = true;
02658                 foreach ( $uniqueIndexes as $index ) {
02659                     if ( $first ) {
02660                         $first = false;
02661                         $sql .= '( ';
02662                     } else {
02663                         $sql .= ' ) OR ( ';
02664                     }
02665                     if ( is_array( $index ) ) {
02666                         $first2 = true;
02667                         foreach ( $index as $col ) {
02668                             if ( $first2 ) {
02669                                 $first2 = false;
02670                             } else {
02671                                 $sql .= ' AND ';
02672                             }
02673                             $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02674                         }
02675                     } else {
02676                         $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02677                     }
02678                 }
02679                 $sql .= ' )';
02680                 $this->query( $sql, $fname );
02681             }
02682 
02683             # Now insert the row
02684             $this->insert( $table, $row, $fname );
02685         }
02686     }
02687 
02698     protected function nativeReplace( $table, $rows, $fname ) {
02699         $table = $this->tableName( $table );
02700 
02701         # Single row case
02702         if ( !is_array( reset( $rows ) ) ) {
02703             $rows = array( $rows );
02704         }
02705 
02706         $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02707         $first = true;
02708 
02709         foreach ( $rows as $row ) {
02710             if ( $first ) {
02711                 $first = false;
02712             } else {
02713                 $sql .= ',';
02714             }
02715 
02716             $sql .= '(' . $this->makeList( $row ) . ')';
02717         }
02718 
02719         return $this->query( $sql, $fname );
02720     }
02721 
02756     public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
02757         $fname = __METHOD__
02758     ) {
02759         if ( !count( $rows ) ) {
02760             return true; // nothing to do
02761         }
02762 
02763         if ( !is_array( reset( $rows ) ) ) {
02764             $rows = array( $rows );
02765         }
02766 
02767         if ( count( $uniqueIndexes ) ) {
02768             $clauses = array(); // list WHERE clauses that each identify a single row
02769             foreach ( $rows as $row ) {
02770                 foreach ( $uniqueIndexes as $index ) {
02771                     $index = is_array( $index ) ? $index : array( $index ); // columns
02772                     $rowKey = array(); // unique key to this row
02773                     foreach ( $index as $column ) {
02774                         $rowKey[$column] = $row[$column];
02775                     }
02776                     $clauses[] = $this->makeList( $rowKey, LIST_AND );
02777                 }
02778             }
02779             $where = array( $this->makeList( $clauses, LIST_OR ) );
02780         } else {
02781             $where = false;
02782         }
02783 
02784         $useTrx = !$this->mTrxLevel;
02785         if ( $useTrx ) {
02786             $this->begin( $fname );
02787         }
02788         try {
02789             # Update any existing conflicting row(s)
02790             if ( $where !== false ) {
02791                 $ok = $this->update( $table, $set, $where, $fname );
02792             } else {
02793                 $ok = true;
02794             }
02795             # Now insert any non-conflicting row(s)
02796             $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok;
02797         } catch ( Exception $e ) {
02798             if ( $useTrx ) {
02799                 $this->rollback( $fname );
02800             }
02801             throw $e;
02802         }
02803         if ( $useTrx ) {
02804             $this->commit( $fname );
02805         }
02806 
02807         return $ok;
02808     }
02809 
02830     public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02831         $fname = __METHOD__
02832     ) {
02833         if ( !$conds ) {
02834             throw new DBUnexpectedError( $this,
02835                 'DatabaseBase::deleteJoin() called with empty $conds' );
02836         }
02837 
02838         $delTable = $this->tableName( $delTable );
02839         $joinTable = $this->tableName( $joinTable );
02840         $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02841         if ( $conds != '*' ) {
02842             $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02843         }
02844         $sql .= ')';
02845 
02846         $this->query( $sql, $fname );
02847     }
02848 
02856     public function textFieldSize( $table, $field ) {
02857         $table = $this->tableName( $table );
02858         $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02859         $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02860         $row = $this->fetchObject( $res );
02861 
02862         $m = array();
02863 
02864         if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02865             $size = $m[1];
02866         } else {
02867             $size = -1;
02868         }
02869 
02870         return $size;
02871     }
02872 
02881     public function lowPriorityOption() {
02882         return '';
02883     }
02884 
02895     public function delete( $table, $conds, $fname = __METHOD__ ) {
02896         if ( !$conds ) {
02897             throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02898         }
02899 
02900         $table = $this->tableName( $table );
02901         $sql = "DELETE FROM $table";
02902 
02903         if ( $conds != '*' ) {
02904             if ( is_array( $conds ) ) {
02905                 $conds = $this->makeList( $conds, LIST_AND );
02906             }
02907             $sql .= ' WHERE ' . $conds;
02908         }
02909 
02910         return $this->query( $sql, $fname );
02911     }
02912 
02939     public function insertSelect( $destTable, $srcTable, $varMap, $conds,
02940         $fname = __METHOD__,
02941         $insertOptions = array(), $selectOptions = array()
02942     ) {
02943         $destTable = $this->tableName( $destTable );
02944 
02945         if ( !is_array( $insertOptions ) ) {
02946             $insertOptions = array( $insertOptions );
02947         }
02948 
02949         $insertOptions = $this->makeInsertOptions( $insertOptions );
02950 
02951         if ( !is_array( $selectOptions ) ) {
02952             $selectOptions = array( $selectOptions );
02953         }
02954 
02955         list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
02956 
02957         if ( is_array( $srcTable ) ) {
02958             $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
02959         } else {
02960             $srcTable = $this->tableName( $srcTable );
02961         }
02962 
02963         $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
02964             " SELECT $startOpts " . implode( ',', $varMap ) .
02965             " FROM $srcTable $useIndex ";
02966 
02967         if ( $conds != '*' ) {
02968             if ( is_array( $conds ) ) {
02969                 $conds = $this->makeList( $conds, LIST_AND );
02970             }
02971             $sql .= " WHERE $conds";
02972         }
02973 
02974         $sql .= " $tailOpts";
02975 
02976         return $this->query( $sql, $fname );
02977     }
02978 
02998     public function limitResult( $sql, $limit, $offset = false ) {
02999         if ( !is_numeric( $limit ) ) {
03000             throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
03001         }
03002 
03003         return "$sql LIMIT "
03004             . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
03005             . "{$limit} ";
03006     }
03007 
03013     public function unionSupportsOrderAndLimit() {
03014         return true; // True for almost every DB supported
03015     }
03016 
03025     public function unionQueries( $sqls, $all ) {
03026         $glue = $all ? ') UNION ALL (' : ') UNION (';
03027 
03028         return '(' . implode( $glue, $sqls ) . ')';
03029     }
03030 
03040     public function conditional( $cond, $trueVal, $falseVal ) {
03041         if ( is_array( $cond ) ) {
03042             $cond = $this->makeList( $cond, LIST_AND );
03043         }
03044 
03045         return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
03046     }
03047 
03058     public function strreplace( $orig, $old, $new ) {
03059         return "REPLACE({$orig}, {$old}, {$new})";
03060     }
03061 
03068     public function getServerUptime() {
03069         return 0;
03070     }
03071 
03078     public function wasDeadlock() {
03079         return false;
03080     }
03081 
03088     public function wasLockTimeout() {
03089         return false;
03090     }
03091 
03099     public function wasErrorReissuable() {
03100         return false;
03101     }
03102 
03109     public function wasReadOnlyError() {
03110         return false;
03111     }
03112 
03131     public function deadlockLoop() {
03132         $this->begin( __METHOD__ );
03133         $args = func_get_args();
03134         $function = array_shift( $args );
03135         $oldIgnore = $this->ignoreErrors( true );
03136         $tries = self::DEADLOCK_TRIES;
03137 
03138         if ( is_array( $function ) ) {
03139             $fname = $function[0];
03140         } else {
03141             $fname = $function;
03142         }
03143 
03144         do {
03145             $retVal = call_user_func_array( $function, $args );
03146             $error = $this->lastError();
03147             $errno = $this->lastErrno();
03148             $sql = $this->lastQuery();
03149 
03150             if ( $errno ) {
03151                 if ( $this->wasDeadlock() ) {
03152                     # Retry
03153                     usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
03154                 } else {
03155                     $this->reportQueryError( $error, $errno, $sql, $fname );
03156                 }
03157             }
03158         } while ( $this->wasDeadlock() && --$tries > 0 );
03159 
03160         $this->ignoreErrors( $oldIgnore );
03161 
03162         if ( $tries <= 0 ) {
03163             $this->rollback( __METHOD__ );
03164             $this->reportQueryError( $error, $errno, $sql, $fname );
03165 
03166             return false;
03167         } else {
03168             $this->commit( __METHOD__ );
03169 
03170             return $retVal;
03171         }
03172     }
03173 
03184     public function masterPosWait( DBMasterPos $pos, $timeout ) {
03185         # Real waits are implemented in the subclass.
03186         return 0;
03187     }
03188 
03194     public function getSlavePos() {
03195         # Stub
03196         return false;
03197     }
03198 
03204     public function getMasterPos() {
03205         # Stub
03206         return false;
03207     }
03208 
03223     final public function onTransactionIdle( $callback ) {
03224         $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() );
03225         if ( !$this->mTrxLevel ) {
03226             $this->runOnTransactionIdleCallbacks();
03227         }
03228     }
03229 
03241     final public function onTransactionPreCommitOrIdle( $callback ) {
03242         if ( $this->mTrxLevel ) {
03243             $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() );
03244         } else {
03245             $this->onTransactionIdle( $callback ); // this will trigger immediately
03246         }
03247     }
03248 
03254     protected function runOnTransactionIdleCallbacks() {
03255         $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
03256 
03257         $e = null; // last exception
03258         do { // callbacks may add callbacks :)
03259             $callbacks = $this->mTrxIdleCallbacks;
03260             $this->mTrxIdleCallbacks = array(); // recursion guard
03261             foreach ( $callbacks as $callback ) {
03262                 try {
03263                     list( $phpCallback ) = $callback;
03264                     $this->clearFlag( DBO_TRX ); // make each query its own transaction
03265                     call_user_func( $phpCallback );
03266                     $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
03267                 } catch ( Exception $e ) {
03268                 }
03269             }
03270         } while ( count( $this->mTrxIdleCallbacks ) );
03271 
03272         if ( $e instanceof Exception ) {
03273             throw $e; // re-throw any last exception
03274         }
03275     }
03276 
03282     protected function runOnTransactionPreCommitCallbacks() {
03283         $e = null; // last exception
03284         do { // callbacks may add callbacks :)
03285             $callbacks = $this->mTrxPreCommitCallbacks;
03286             $this->mTrxPreCommitCallbacks = array(); // recursion guard
03287             foreach ( $callbacks as $callback ) {
03288                 try {
03289                     list( $phpCallback ) = $callback;
03290                     call_user_func( $phpCallback );
03291                 } catch ( Exception $e ) {
03292                 }
03293             }
03294         } while ( count( $this->mTrxPreCommitCallbacks ) );
03295 
03296         if ( $e instanceof Exception ) {
03297             throw $e; // re-throw any last exception
03298         }
03299     }
03300 
03325     final public function startAtomic( $fname = __METHOD__ ) {
03326         if ( !$this->mTrxLevel ) {
03327             $this->begin( $fname );
03328             $this->mTrxAutomatic = true;
03329             $this->mTrxAutomaticAtomic = true;
03330         }
03331 
03332         $this->mTrxAtomicLevels->push( $fname );
03333     }
03334 
03346     final public function endAtomic( $fname = __METHOD__ ) {
03347         if ( !$this->mTrxLevel ) {
03348             throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
03349         }
03350         if ( $this->mTrxAtomicLevels->isEmpty() ||
03351             $this->mTrxAtomicLevels->pop() !== $fname
03352         ) {
03353             throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
03354         }
03355 
03356         if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) {
03357             $this->commit( $fname, 'flush' );
03358         }
03359     }
03360 
03376     final public function begin( $fname = __METHOD__ ) {
03377         global $wgDebugDBTransactions;
03378 
03379         if ( $this->mTrxLevel ) { // implicit commit
03380             if ( !$this->mTrxAtomicLevels->isEmpty() ) {
03381                 // If the current transaction was an automatic atomic one, then we definitely have
03382                 // a problem. Same if there is any unclosed atomic level.
03383                 throw new DBUnexpectedError( $this,
03384                     "Attempted to start explicit transaction when atomic levels are still open."
03385                 );
03386             } elseif ( !$this->mTrxAutomatic ) {
03387                 // We want to warn about inadvertently nested begin/commit pairs, but not about
03388                 // auto-committing implicit transactions that were started by query() via DBO_TRX
03389                 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
03390                     " performing implicit commit!";
03391                 wfWarn( $msg );
03392                 wfLogDBError( $msg );
03393             } else {
03394                 // if the transaction was automatic and has done write operations,
03395                 // log it if $wgDebugDBTransactions is enabled.
03396                 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
03397                     wfDebug( "$fname: Automatic transaction with writes in progress" .
03398                         " (from {$this->mTrxFname}), performing implicit commit!\n"
03399                     );
03400                 }
03401             }
03402 
03403             $this->runOnTransactionPreCommitCallbacks();
03404             $this->doCommit( $fname );
03405             if ( $this->mTrxDoneWrites ) {
03406                 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
03407             }
03408             $this->runOnTransactionIdleCallbacks();
03409         }
03410 
03411         $this->doBegin( $fname );
03412         $this->mTrxFname = $fname;
03413         $this->mTrxDoneWrites = false;
03414         $this->mTrxAutomatic = false;
03415         $this->mTrxAutomaticAtomic = false;
03416         $this->mTrxAtomicLevels = new SplStack;
03417         $this->mTrxIdleCallbacks = array();
03418         $this->mTrxPreCommitCallbacks = array();
03419     }
03420 
03427     protected function doBegin( $fname ) {
03428         $this->query( 'BEGIN', $fname );
03429         $this->mTrxLevel = 1;
03430     }
03431 
03446     final public function commit( $fname = __METHOD__, $flush = '' ) {
03447         if ( !$this->mTrxAtomicLevels->isEmpty() ) {
03448             // There are still atomic sections open. This cannot be ignored
03449             throw new DBUnexpectedError(
03450                 $this,
03451                 "Attempted to commit transaction while atomic sections are still open"
03452             );
03453         }
03454 
03455         if ( $flush !== 'flush' ) {
03456             if ( !$this->mTrxLevel ) {
03457                 wfWarn( "$fname: No transaction to commit, something got out of sync!" );
03458             } elseif ( $this->mTrxAutomatic ) {
03459                 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
03460             }
03461         } else {
03462             if ( !$this->mTrxLevel ) {
03463                 return; // nothing to do
03464             } elseif ( !$this->mTrxAutomatic ) {
03465                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03466             }
03467         }
03468 
03469         $this->runOnTransactionPreCommitCallbacks();
03470         $this->doCommit( $fname );
03471         if ( $this->mTrxDoneWrites ) {
03472             Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
03473         }
03474         $this->runOnTransactionIdleCallbacks();
03475     }
03476 
03483     protected function doCommit( $fname ) {
03484         if ( $this->mTrxLevel ) {
03485             $this->query( 'COMMIT', $fname );
03486             $this->mTrxLevel = 0;
03487         }
03488     }
03489 
03503     final public function rollback( $fname = __METHOD__, $flush = '' ) {
03504         if ( $flush !== 'flush' ) {
03505             if ( !$this->mTrxLevel ) {
03506                 wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
03507             } elseif ( $this->mTrxAutomatic ) {
03508                 wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" );
03509             }
03510         } else {
03511             if ( !$this->mTrxLevel ) {
03512                 return; // nothing to do
03513             } elseif ( !$this->mTrxAutomatic ) {
03514                 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
03515             }
03516         }
03517 
03518         $this->doRollback( $fname );
03519         $this->mTrxIdleCallbacks = array(); // cancel
03520         $this->mTrxPreCommitCallbacks = array(); // cancel
03521         $this->mTrxAtomicLevels = new SplStack;
03522         if ( $this->mTrxDoneWrites ) {
03523             Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
03524         }
03525     }
03526 
03533     protected function doRollback( $fname ) {
03534         if ( $this->mTrxLevel ) {
03535             $this->query( 'ROLLBACK', $fname, true );
03536             $this->mTrxLevel = 0;
03537         }
03538     }
03539 
03555     public function duplicateTableStructure( $oldName, $newName, $temporary = false,
03556         $fname = __METHOD__
03557     ) {
03558         throw new MWException(
03559             'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
03560     }
03561 
03569     function listTables( $prefix = null, $fname = __METHOD__ ) {
03570         throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
03571     }
03572 
03577     final public function clearViewsCache() {
03578         $this->allViews = null;
03579     }
03580 
03592     public function listViews( $prefix = null, $fname = __METHOD__ ) {
03593         throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
03594     }
03595 
03603     public function isView( $name ) {
03604         throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
03605     }
03606 
03618     public function timestamp( $ts = 0 ) {
03619         return wfTimestamp( TS_MW, $ts );
03620     }
03621 
03635     public function timestampOrNull( $ts = null ) {
03636         if ( is_null( $ts ) ) {
03637             return null;
03638         } else {
03639             return $this->timestamp( $ts );
03640         }
03641     }
03642 
03657     public function resultObject( $result ) {
03658         if ( empty( $result ) ) {
03659             return false;
03660         } elseif ( $result instanceof ResultWrapper ) {
03661             return $result;
03662         } elseif ( $result === true ) {
03663             // Successful write query
03664             return $result;
03665         } else {
03666             return new ResultWrapper( $this, $result );
03667         }
03668     }
03669 
03675     public function ping() {
03676         # Stub. Not essential to override.
03677         return true;
03678     }
03679 
03689     public function getLag() {
03690         return 0;
03691     }
03692 
03698     function maxListLen() {
03699         return 0;
03700     }
03701 
03711     public function encodeBlob( $b ) {
03712         return $b;
03713     }
03714 
03723     public function decodeBlob( $b ) {
03724         return $b;
03725     }
03726 
03737     public function setSessionOptions( array $options ) {
03738     }
03739 
03756     public function sourceFile(
03757         $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
03758     ) {
03759         wfSuppressWarnings();
03760         $fp = fopen( $filename, 'r' );
03761         wfRestoreWarnings();
03762 
03763         if ( false === $fp ) {
03764             throw new MWException( "Could not open \"{$filename}\".\n" );
03765         }
03766 
03767         if ( !$fname ) {
03768             $fname = __METHOD__ . "( $filename )";
03769         }
03770 
03771         try {
03772             $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
03773         } catch ( MWException $e ) {
03774             fclose( $fp );
03775             throw $e;
03776         }
03777 
03778         fclose( $fp );
03779 
03780         return $error;
03781     }
03782 
03791     public function patchPath( $patch ) {
03792         global $IP;
03793 
03794         $dbType = $this->getType();
03795         if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03796             return "$IP/maintenance/$dbType/archives/$patch";
03797         } else {
03798             return "$IP/maintenance/archives/$patch";
03799         }
03800     }
03801 
03809     public function setSchemaVars( $vars ) {
03810         $this->mSchemaVars = $vars;
03811     }
03812 
03826     public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03827         $fname = __METHOD__, $inputCallback = false
03828     ) {
03829         $cmd = '';
03830 
03831         while ( !feof( $fp ) ) {
03832             if ( $lineCallback ) {
03833                 call_user_func( $lineCallback );
03834             }
03835 
03836             $line = trim( fgets( $fp ) );
03837 
03838             if ( $line == '' ) {
03839                 continue;
03840             }
03841 
03842             if ( '-' == $line[0] && '-' == $line[1] ) {
03843                 continue;
03844             }
03845 
03846             if ( $cmd != '' ) {
03847                 $cmd .= ' ';
03848             }
03849 
03850             $done = $this->streamStatementEnd( $cmd, $line );
03851 
03852             $cmd .= "$line\n";
03853 
03854             if ( $done || feof( $fp ) ) {
03855                 $cmd = $this->replaceVars( $cmd );
03856 
03857                 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
03858                     $res = $this->query( $cmd, $fname );
03859 
03860                     if ( $resultCallback ) {
03861                         call_user_func( $resultCallback, $res, $this );
03862                     }
03863 
03864                     if ( false === $res ) {
03865                         $err = $this->lastError();
03866 
03867                         return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03868                     }
03869                 }
03870                 $cmd = '';
03871             }
03872         }
03873 
03874         return true;
03875     }
03876 
03884     public function streamStatementEnd( &$sql, &$newLine ) {
03885         if ( $this->delimiter ) {
03886             $prev = $newLine;
03887             $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
03888             if ( $newLine != $prev ) {
03889                 return true;
03890             }
03891         }
03892 
03893         return false;
03894     }
03895 
03913     protected function replaceSchemaVars( $ins ) {
03914         $vars = $this->getSchemaVars();
03915         foreach ( $vars as $var => $value ) {
03916             // replace '{$var}'
03917             $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
03918             // replace `{$var}`
03919             $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
03920             // replace /*$var*/
03921             $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
03922         }
03923 
03924         return $ins;
03925     }
03926 
03933     protected function replaceVars( $ins ) {
03934         $ins = $this->replaceSchemaVars( $ins );
03935 
03936         // Table prefixes
03937         $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
03938             array( $this, 'tableNameCallback' ), $ins );
03939 
03940         // Index names
03941         $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
03942             array( $this, 'indexNameCallback' ), $ins );
03943 
03944         return $ins;
03945     }
03946 
03953     protected function getSchemaVars() {
03954         if ( $this->mSchemaVars ) {
03955             return $this->mSchemaVars;
03956         } else {
03957             return $this->getDefaultSchemaVars();
03958         }
03959     }
03960 
03969     protected function getDefaultSchemaVars() {
03970         return array();
03971     }
03972 
03979     protected function tableNameCallback( $matches ) {
03980         return $this->tableName( $matches[1] );
03981     }
03982 
03989     protected function indexNameCallback( $matches ) {
03990         return $this->indexName( $matches[1] );
03991     }
03992 
04001     public function lockIsFree( $lockName, $method ) {
04002         return true;
04003     }
04004 
04016     public function lock( $lockName, $method, $timeout = 5 ) {
04017         return true;
04018     }
04019 
04030     public function unlock( $lockName, $method ) {
04031         return true;
04032     }
04033 
04043     public function lockTables( $read, $write, $method, $lowPriority = true ) {
04044         return true;
04045     }
04046 
04053     public function unlockTables( $method ) {
04054         return true;
04055     }
04056 
04064     public function dropTable( $tableName, $fName = __METHOD__ ) {
04065         if ( !$this->tableExists( $tableName, $fName ) ) {
04066             return false;
04067         }
04068         $sql = "DROP TABLE " . $this->tableName( $tableName );
04069         if ( $this->cascadingDeletes() ) {
04070             $sql .= " CASCADE";
04071         }
04072 
04073         return $this->query( $sql, $fName );
04074     }
04075 
04082     public function getSearchEngine() {
04083         return 'SearchEngineDummy';
04084     }
04085 
04093     public function getInfinity() {
04094         return 'infinity';
04095     }
04096 
04103     public function encodeExpiry( $expiry ) {
04104         return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
04105             ? $this->getInfinity()
04106             : $this->timestamp( $expiry );
04107     }
04108 
04116     public function decodeExpiry( $expiry, $format = TS_MW ) {
04117         return ( $expiry == '' || $expiry == $this->getInfinity() )
04118             ? 'infinity'
04119             : wfTimestamp( $format, $expiry );
04120     }
04121 
04131     public function setBigSelects( $value = true ) {
04132         // no-op
04133     }
04134 
04138     public function __toString() {
04139         return (string)$this->mConn;
04140     }
04141 
04145     public function __destruct() {
04146         if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
04147             trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
04148         }
04149         if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
04150             $callers = array();
04151             foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
04152                 $callers[] = $callbackInfo[1];
04153             }
04154             $callers = implode( ', ', $callers );
04155             trigger_error( "DB transaction callbacks still pending (from $callers)." );
04156         }
04157     }
04158 }