MediaWiki  REL1_20
Database.php
Go to the documentation of this file.
00001 <?php
00028 define( 'DEADLOCK_TRIES', 4 );
00030 define( 'DEADLOCK_DELAY_MIN', 500000 );
00032 define( 'DEADLOCK_DELAY_MAX', 1500000 );
00033 
00041 interface DatabaseType {
00047         function getType();
00048 
00059         function open( $server, $user, $password, $dbName );
00060 
00070         function fetchObject( $res );
00071 
00080         function fetchRow( $res );
00081 
00088         function numRows( $res );
00089 
00097         function numFields( $res );
00098 
00107         function fieldName( $res, $n );
00108 
00121         function insertId();
00122 
00130         function dataSeek( $res, $row );
00131 
00138         function lastErrno();
00139 
00146         function lastError();
00147 
00157         function fieldInfo( $table, $field );
00158 
00166         function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
00167 
00174         function affectedRows();
00175 
00182         function strencode( $s );
00183 
00192         static function getSoftwareLink();
00193 
00200         function getServerVersion();
00201 
00209         function getServerInfo();
00210 }
00211 
00216 abstract class DatabaseBase implements DatabaseType {
00217 
00218 # ------------------------------------------------------------------------------
00219 # Variables
00220 # ------------------------------------------------------------------------------
00221 
00222         protected $mLastQuery = '';
00223         protected $mDoneWrites = false;
00224         protected $mPHPError = false;
00225 
00226         protected $mServer, $mUser, $mPassword, $mDBname;
00227 
00228         protected $mConn = null;
00229         protected $mOpened = false;
00230 
00235         protected $mTrxIdleCallbacks = array();
00236 
00237         protected $mTablePrefix;
00238         protected $mFlags;
00239         protected $mTrxLevel = 0;
00240         protected $mErrorCount = 0;
00241         protected $mLBInfo = array();
00242         protected $mFakeSlaveLag = null, $mFakeMaster = false;
00243         protected $mDefaultBigSelects = null;
00244         protected $mSchemaVars = false;
00245 
00246         protected $preparedArgs;
00247 
00248         protected $htmlErrors;
00249 
00250         protected $delimiter = ';';
00251 
00252 # ------------------------------------------------------------------------------
00253 # Accessors
00254 # ------------------------------------------------------------------------------
00255         # These optionally set a variable and return the previous state
00256 
00264         public function getServerInfo() {
00265                 return $this->getServerVersion();
00266         }
00267 
00277         public function debug( $debug = null ) {
00278                 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00279         }
00280 
00303         public function bufferResults( $buffer = null ) {
00304                 if ( is_null( $buffer ) ) {
00305                         return !(bool)( $this->mFlags & DBO_NOBUFFER );
00306                 } else {
00307                         return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00308                 }
00309         }
00310 
00322         public function ignoreErrors( $ignoreErrors = null ) {
00323                 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00324         }
00325 
00335         public function trxLevel( $level = null ) {
00336                 return wfSetVar( $this->mTrxLevel, $level );
00337         }
00338 
00344         public function errorCount( $count = null ) {
00345                 return wfSetVar( $this->mErrorCount, $count );
00346         }
00347 
00353         public function tablePrefix( $prefix = null ) {
00354                 return wfSetVar( $this->mTablePrefix, $prefix );
00355         }
00356 
00366         public function getLBInfo( $name = null ) {
00367                 if ( is_null( $name ) ) {
00368                         return $this->mLBInfo;
00369                 } else {
00370                         if ( array_key_exists( $name, $this->mLBInfo ) ) {
00371                                 return $this->mLBInfo[$name];
00372                         } else {
00373                                 return null;
00374                         }
00375                 }
00376         }
00377 
00386         public function setLBInfo( $name, $value = null ) {
00387                 if ( is_null( $value ) ) {
00388                         $this->mLBInfo = $name;
00389                 } else {
00390                         $this->mLBInfo[$name] = $value;
00391                 }
00392         }
00393 
00399         public function setFakeSlaveLag( $lag ) {
00400                 $this->mFakeSlaveLag = $lag;
00401         }
00402 
00408         public function setFakeMaster( $enabled = true ) {
00409                 $this->mFakeMaster = $enabled;
00410         }
00411 
00417         public function cascadingDeletes() {
00418                 return false;
00419         }
00420 
00426         public function cleanupTriggers() {
00427                 return false;
00428         }
00429 
00436         public function strictIPs() {
00437                 return false;
00438         }
00439 
00445         public function realTimestamps() {
00446                 return false;
00447         }
00448 
00454         public function implicitGroupby() {
00455                 return true;
00456         }
00457 
00464         public function implicitOrderby() {
00465                 return true;
00466         }
00467 
00474         public function searchableIPs() {
00475                 return false;
00476         }
00477 
00483         public function functionalIndexes() {
00484                 return false;
00485         }
00486 
00491         public function lastQuery() {
00492                 return $this->mLastQuery;
00493         }
00494 
00501         public function doneWrites() {
00502                 return $this->mDoneWrites;
00503         }
00504 
00511         public function writesOrCallbacksPending() {
00512                 return $this->mTrxLevel && ( $this->mDoneWrites || $this->mTrxIdleCallbacks );
00513         }
00514 
00519         public function isOpen() {
00520                 return $this->mOpened;
00521         }
00522 
00535         public function setFlag( $flag ) {
00536                 global $wgDebugDBTransactions;
00537                 $this->mFlags |= $flag;
00538                 if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
00539                         wfDebug("Implicit transactions are now  disabled.\n");
00540                 }
00541         }
00542 
00548         public function clearFlag( $flag ) {
00549                 global $wgDebugDBTransactions;
00550                 $this->mFlags &= ~$flag;
00551                 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
00552                         wfDebug("Implicit transactions are now disabled.\n");
00553                 }
00554         }
00555 
00562         public function getFlag( $flag ) {
00563                 return !!( $this->mFlags & $flag );
00564         }
00565 
00573         public function getProperty( $name ) {
00574                 return $this->$name;
00575         }
00576 
00580         public function getWikiID() {
00581                 if ( $this->mTablePrefix ) {
00582                         return "{$this->mDBname}-{$this->mTablePrefix}";
00583                 } else {
00584                         return $this->mDBname;
00585                 }
00586         }
00587 
00593         public function getSchemaPath() {
00594                 global $IP;
00595                 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
00596                         return "$IP/maintenance/" . $this->getType() . "/tables.sql";
00597                 } else {
00598                         return "$IP/maintenance/tables.sql";
00599                 }
00600         }
00601 
00602 # ------------------------------------------------------------------------------
00603 # Other functions
00604 # ------------------------------------------------------------------------------
00605 
00615         function __construct( $server = false, $user = false, $password = false, $dbName = false,
00616                 $flags = 0, $tablePrefix = 'get from global'
00617         ) {
00618                 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
00619 
00620                 $this->mFlags = $flags;
00621 
00622                 if ( $this->mFlags & DBO_DEFAULT ) {
00623                         if ( $wgCommandLineMode ) {
00624                                 $this->mFlags &= ~DBO_TRX;
00625                                 if ( $wgDebugDBTransactions ) {
00626                                         wfDebug("Implicit transaction open disabled.\n");
00627                                 }
00628                         } else {
00629                                 $this->mFlags |= DBO_TRX;
00630                                 if ( $wgDebugDBTransactions ) {
00631                                         wfDebug("Implicit transaction open enabled.\n");
00632                                 }
00633                         }
00634                 }
00635 
00637                 if ( $tablePrefix == 'get from global' ) {
00638                         $this->mTablePrefix = $wgDBprefix;
00639                 } else {
00640                         $this->mTablePrefix = $tablePrefix;
00641                 }
00642 
00643                 if ( $user ) {
00644                         $this->open( $server, $user, $password, $dbName );
00645                 }
00646         }
00647 
00653         public function __sleep() {
00654                 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
00655         }
00656 
00679         public final static function factory( $dbType, $p = array() ) {
00680                 $canonicalDBTypes = array(
00681                         'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
00682                 );
00683                 $dbType = strtolower( $dbType );
00684                 $class = 'Database' . ucfirst( $dbType );
00685 
00686                 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
00687                         return new $class(
00688                                 isset( $p['host'] ) ? $p['host'] : false,
00689                                 isset( $p['user'] ) ? $p['user'] : false,
00690                                 isset( $p['password'] ) ? $p['password'] : false,
00691                                 isset( $p['dbname'] ) ? $p['dbname'] : false,
00692                                 isset( $p['flags'] ) ? $p['flags'] : 0,
00693                                 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
00694                         );
00695                 } else {
00696                         return null;
00697                 }
00698         }
00699 
00700         protected function installErrorHandler() {
00701                 $this->mPHPError = false;
00702                 $this->htmlErrors = ini_set( 'html_errors', '0' );
00703                 set_error_handler( array( $this, 'connectionErrorHandler' ) );
00704         }
00705 
00709         protected function restoreErrorHandler() {
00710                 restore_error_handler();
00711                 if ( $this->htmlErrors !== false ) {
00712                         ini_set( 'html_errors', $this->htmlErrors );
00713                 }
00714                 if ( $this->mPHPError ) {
00715                         $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00716                         $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
00717                         return $error;
00718                 } else {
00719                         return false;
00720                 }
00721         }
00722 
00727         protected function connectionErrorHandler( $errno,  $errstr ) {
00728                 $this->mPHPError = $errstr;
00729         }
00730 
00737         public function close() {
00738                 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
00739                         throw new MWException( "Transaction idle callbacks still pending." );
00740                 }
00741                 $this->mOpened = false;
00742                 if ( $this->mConn ) {
00743                         if ( $this->trxLevel() ) {
00744                                 $this->commit( __METHOD__ );
00745                         }
00746                         $ret = $this->closeConnection();
00747                         $this->mConn = false;
00748                         return $ret;
00749                 } else {
00750                         return true;
00751                 }
00752         }
00753 
00759         protected abstract function closeConnection();
00760 
00764         function reportConnectionError( $error = 'Unknown error' ) {
00765                 $myError = $this->lastError();
00766                 if ( $myError ) {
00767                         $error = $myError;
00768                 }
00769 
00770                 # New method
00771                 throw new DBConnectionError( $this, $error );
00772         }
00773 
00780         protected abstract function doQuery( $sql );
00781 
00790         public function isWriteQuery( $sql ) {
00791                 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
00792         }
00793 
00816         public function query( $sql, $fname = '', $tempIgnore = false ) {
00817                 $isMaster = !is_null( $this->getLBInfo( 'master' ) );
00818                 if ( !Profiler::instance()->isStub() ) {
00819                         # generalizeSQL will probably cut down the query to reasonable
00820                         # logging size most of the time. The substr is really just a sanity check.
00821 
00822                         if ( $isMaster ) {
00823                                 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00824                                 $totalProf = 'DatabaseBase::query-master';
00825                         } else {
00826                                 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
00827                                 $totalProf = 'DatabaseBase::query';
00828                         }
00829 
00830                         wfProfileIn( $totalProf );
00831                         wfProfileIn( $queryProf );
00832                 }
00833 
00834                 $this->mLastQuery = $sql;
00835                 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
00836                         # Set a flag indicating that writes have been done
00837                         wfDebug( __METHOD__ . ": Writes done: $sql\n" );
00838                         $this->mDoneWrites = true;
00839                 }
00840 
00841                 # Add a comment for easy SHOW PROCESSLIST interpretation
00842                 global $wgUser;
00843                 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
00844                         $userName = $wgUser->getName();
00845                         if ( mb_strlen( $userName ) > 15 ) {
00846                                 $userName = mb_substr( $userName, 0, 15 ) . '...';
00847                         }
00848                         $userName = str_replace( '/', '', $userName );
00849                 } else {
00850                         $userName = '';
00851                 }
00852                 $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
00853 
00854                 # If DBO_TRX is set, start a transaction
00855                 if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
00856                         $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) {
00857                         # avoid establishing transactions for SHOW and SET statements too -
00858                         # that would delay transaction initializations to once connection
00859                         # is really used by application
00860                         $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
00861                         if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
00862                                 global $wgDebugDBTransactions;
00863                                 if ( $wgDebugDBTransactions ) {
00864                                         wfDebug("Implicit transaction start.\n");
00865                                 }
00866                                 $this->begin( __METHOD__ . " ($fname)" );
00867                         }
00868                 }
00869 
00870                 if ( $this->debug() ) {
00871                         static $cnt = 0;
00872 
00873                         $cnt++;
00874                         $sqlx = substr( $commentedSql, 0, 500 );
00875                         $sqlx = strtr( $sqlx, "\t\n", '  ' );
00876 
00877                         $master = $isMaster ? 'master' : 'slave';
00878                         wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
00879                 }
00880 
00881                 if ( istainted( $sql ) & TC_MYSQL ) {
00882                         throw new MWException( 'Tainted query found' );
00883                 }
00884 
00885                 $queryId = MWDebug::query( $sql, $fname, $isMaster );
00886 
00887                 # Do the query and handle errors
00888                 $ret = $this->doQuery( $commentedSql );
00889 
00890                 MWDebug::queryTime( $queryId );
00891 
00892                 # Try reconnecting if the connection was lost
00893                 if ( false === $ret && $this->wasErrorReissuable() ) {
00894                         # Transaction is gone, like it or not
00895                         $this->mTrxLevel = 0;
00896                         $this->mTrxIdleCallbacks = array(); // cancel
00897                         wfDebug( "Connection lost, reconnecting...\n" );
00898 
00899                         if ( $this->ping() ) {
00900                                 wfDebug( "Reconnected\n" );
00901                                 $sqlx = substr( $commentedSql, 0, 500 );
00902                                 $sqlx = strtr( $sqlx, "\t\n", '  ' );
00903                                 global $wgRequestTime;
00904                                 $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
00905                                 if ( $elapsed < 300 ) {
00906                                         # Not a database error to lose a transaction after a minute or two
00907                                         wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
00908                                 }
00909                                 $ret = $this->doQuery( $commentedSql );
00910                         } else {
00911                                 wfDebug( "Failed\n" );
00912                         }
00913                 }
00914 
00915                 if ( false === $ret ) {
00916                         $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
00917                 }
00918 
00919                 if ( !Profiler::instance()->isStub() ) {
00920                         wfProfileOut( $queryProf );
00921                         wfProfileOut( $totalProf );
00922                 }
00923 
00924                 return $this->resultObject( $ret );
00925         }
00926 
00937         public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
00938                 # Ignore errors during error handling to avoid infinite recursion
00939                 $ignore = $this->ignoreErrors( true );
00940                 ++$this->mErrorCount;
00941 
00942                 if ( $ignore || $tempIgnore ) {
00943                         wfDebug( "SQL ERROR (ignored): $error\n" );
00944                         $this->ignoreErrors( $ignore );
00945                 } else {
00946                         $sql1line = str_replace( "\n", "\\n", $sql );
00947                         wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
00948                         wfDebug( "SQL ERROR: " . $error . "\n" );
00949                         throw new DBQueryError( $this, $error, $errno, $sql, $fname );
00950                 }
00951         }
00952 
00967         protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
00968                 /* MySQL doesn't support prepared statements (yet), so just
00969                    pack up the query for reference. We'll manually replace
00970                    the bits later. */
00971                 return array( 'query' => $sql, 'func' => $func );
00972         }
00973 
00978         protected function freePrepared( $prepared ) {
00979                 /* No-op by default */
00980         }
00981 
00989         public function execute( $prepared, $args = null ) {
00990                 if ( !is_array( $args ) ) {
00991                         # Pull the var args
00992                         $args = func_get_args();
00993                         array_shift( $args );
00994                 }
00995 
00996                 $sql = $this->fillPrepared( $prepared['query'], $args );
00997 
00998                 return $this->query( $sql, $prepared['func'] );
00999         }
01000 
01008         public function fillPrepared( $preparedQuery, $args ) {
01009                 reset( $args );
01010                 $this->preparedArgs =& $args;
01011 
01012                 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
01013                         array( &$this, 'fillPreparedArg' ), $preparedQuery );
01014         }
01015 
01024         protected function fillPreparedArg( $matches ) {
01025                 switch( $matches[1] ) {
01026                         case '\\?': return '?';
01027                         case '\\!': return '!';
01028                         case '\\&': return '&';
01029                 }
01030 
01031                 list( /* $n */ , $arg ) = each( $this->preparedArgs );
01032 
01033                 switch( $matches[1] ) {
01034                         case '?': return $this->addQuotes( $arg );
01035                         case '!': return $arg;
01036                         case '&':
01037                                 # return $this->addQuotes( file_get_contents( $arg ) );
01038                                 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
01039                         default:
01040                                 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
01041                 }
01042         }
01043 
01051         public function freeResult( $res ) {}
01052 
01070         public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
01071                 $options = array() )
01072         {
01073                 if ( !is_array( $options ) ) {
01074                         $options = array( $options );
01075                 }
01076 
01077                 $options['LIMIT'] = 1;
01078 
01079                 $res = $this->select( $table, $var, $cond, $fname, $options );
01080 
01081                 if ( $res === false || !$this->numRows( $res ) ) {
01082                         return false;
01083                 }
01084 
01085                 $row = $this->fetchRow( $res );
01086 
01087                 if ( $row !== false ) {
01088                         return reset( $row );
01089                 } else {
01090                         return false;
01091                 }
01092         }
01093 
01103         public function makeSelectOptions( $options ) {
01104                 $preLimitTail = $postLimitTail = '';
01105                 $startOpts = '';
01106 
01107                 $noKeyOptions = array();
01108 
01109                 foreach ( $options as $key => $option ) {
01110                         if ( is_numeric( $key ) ) {
01111                                 $noKeyOptions[$option] = true;
01112                         }
01113                 }
01114 
01115                 if ( isset( $options['GROUP BY'] ) ) {
01116                         $gb = is_array( $options['GROUP BY'] )
01117                                 ? implode( ',', $options['GROUP BY'] )
01118                                 : $options['GROUP BY'];
01119                         $preLimitTail .= " GROUP BY {$gb}";
01120                 }
01121 
01122                 if ( isset( $options['HAVING'] ) ) {
01123                         $having = is_array( $options['HAVING'] )
01124                                 ? $this->makeList( $options['HAVING'], LIST_AND )
01125                                 : $options['HAVING'];
01126                         $preLimitTail .= " HAVING {$having}";
01127                 }
01128 
01129                 if ( isset( $options['ORDER BY'] ) ) {
01130                         $ob = is_array( $options['ORDER BY'] )
01131                                 ? implode( ',', $options['ORDER BY'] )
01132                                 : $options['ORDER BY'];
01133                         $preLimitTail .= " ORDER BY {$ob}";
01134                 }
01135 
01136                 // if (isset($options['LIMIT'])) {
01137                 //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
01138                 //              isset($options['OFFSET']) ? $options['OFFSET']
01139                 //              : false);
01140                 // }
01141 
01142                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01143                         $postLimitTail .= ' FOR UPDATE';
01144                 }
01145 
01146                 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
01147                         $postLimitTail .= ' LOCK IN SHARE MODE';
01148                 }
01149 
01150                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01151                         $startOpts .= 'DISTINCT';
01152                 }
01153 
01154                 # Various MySQL extensions
01155                 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
01156                         $startOpts .= ' /*! STRAIGHT_JOIN */';
01157                 }
01158 
01159                 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
01160                         $startOpts .= ' HIGH_PRIORITY';
01161                 }
01162 
01163                 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
01164                         $startOpts .= ' SQL_BIG_RESULT';
01165                 }
01166 
01167                 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
01168                         $startOpts .= ' SQL_BUFFER_RESULT';
01169                 }
01170 
01171                 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
01172                         $startOpts .= ' SQL_SMALL_RESULT';
01173                 }
01174 
01175                 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
01176                         $startOpts .= ' SQL_CALC_FOUND_ROWS';
01177                 }
01178 
01179                 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
01180                         $startOpts .= ' SQL_CACHE';
01181                 }
01182 
01183                 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
01184                         $startOpts .= ' SQL_NO_CACHE';
01185                 }
01186 
01187                 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
01188                         $useIndex = $this->useIndexClause( $options['USE INDEX'] );
01189                 } else {
01190                         $useIndex = '';
01191                 }
01192 
01193                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01194         }
01195 
01335         public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01336                 $options = array(), $join_conds = array() ) {
01337                 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
01338 
01339                 return $this->query( $sql, $fname );
01340         }
01341 
01358         public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
01359                 $options = array(), $join_conds = array() )
01360         {
01361                 if ( is_array( $vars ) ) {
01362                         $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
01363                 }
01364 
01365                 $options = (array)$options;
01366 
01367                 if ( is_array( $table ) ) {
01368                         $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
01369                                 ? $options['USE INDEX']
01370                                 : array();
01371                         if ( count( $join_conds ) || count( $useIndex ) ) {
01372                                 $from = ' FROM ' .
01373                                         $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
01374                         } else {
01375                                 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
01376                         }
01377                 } elseif ( $table != '' ) {
01378                         if ( $table[0] == ' ' ) {
01379                                 $from = ' FROM ' . $table;
01380                         } else {
01381                                 $from = ' FROM ' . $this->tableName( $table );
01382                         }
01383                 } else {
01384                         $from = '';
01385                 }
01386 
01387                 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
01388 
01389                 if ( !empty( $conds ) ) {
01390                         if ( is_array( $conds ) ) {
01391                                 $conds = $this->makeList( $conds, LIST_AND );
01392                         }
01393                         $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
01394                 } else {
01395                         $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
01396                 }
01397 
01398                 if ( isset( $options['LIMIT'] ) ) {
01399                         $sql = $this->limitResult( $sql, $options['LIMIT'],
01400                                 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
01401                 }
01402                 $sql = "$sql $postLimitTail";
01403 
01404                 if ( isset( $options['EXPLAIN'] ) ) {
01405                         $sql = 'EXPLAIN ' . $sql;
01406                 }
01407 
01408                 return $sql;
01409         }
01410 
01425         public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
01426                 $options = array(), $join_conds = array() )
01427         {
01428                 $options = (array)$options;
01429                 $options['LIMIT'] = 1;
01430                 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
01431 
01432                 if ( $res === false ) {
01433                         return false;
01434                 }
01435 
01436                 if ( !$this->numRows( $res ) ) {
01437                         return false;
01438                 }
01439 
01440                 $obj = $this->fetchObject( $res );
01441 
01442                 return $obj;
01443         }
01444 
01465         public function estimateRowCount( $table, $vars = '*', $conds = '',
01466                 $fname = 'DatabaseBase::estimateRowCount', $options = array() )
01467         {
01468                 $rows = 0;
01469                 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
01470 
01471                 if ( $res ) {
01472                         $row = $this->fetchRow( $res );
01473                         $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
01474                 }
01475 
01476                 return $rows;
01477         }
01478 
01487         static function generalizeSQL( $sql ) {
01488                 # This does the same as the regexp below would do, but in such a way
01489                 # as to avoid crashing php on some large strings.
01490                 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
01491 
01492                 $sql = str_replace ( "\\\\", '', $sql );
01493                 $sql = str_replace ( "\\'", '', $sql );
01494                 $sql = str_replace ( "\\\"", '', $sql );
01495                 $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
01496                 $sql = preg_replace ( '/".*"/s', "'X'", $sql );
01497 
01498                 # All newlines, tabs, etc replaced by single space
01499                 $sql = preg_replace ( '/\s+/', ' ', $sql );
01500 
01501                 # All numbers => N
01502                 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
01503 
01504                 return $sql;
01505         }
01506 
01515         public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
01516                 $info = $this->fieldInfo( $table, $field );
01517 
01518                 return (bool)$info;
01519         }
01520 
01532         public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
01533                 $info = $this->indexInfo( $table, $index, $fname );
01534                 if ( is_null( $info ) ) {
01535                         return null;
01536                 } else {
01537                         return $info !== false;
01538                 }
01539         }
01540 
01549         public function tableExists( $table, $fname = __METHOD__ ) {
01550                 $table = $this->tableName( $table );
01551                 $old = $this->ignoreErrors( true );
01552                 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
01553                 $this->ignoreErrors( $old );
01554 
01555                 return (bool)$res;
01556         }
01557 
01564         public function fieldType( $res, $index ) {
01565                 if ( $res instanceof ResultWrapper ) {
01566                         $res = $res->result;
01567                 }
01568 
01569                 return mysql_field_type( $res, $index );
01570         }
01571 
01580         public function indexUnique( $table, $index ) {
01581                 $indexInfo = $this->indexInfo( $table, $index );
01582 
01583                 if ( !$indexInfo ) {
01584                         return null;
01585                 }
01586 
01587                 return !$indexInfo[0]->Non_unique;
01588         }
01589 
01596         protected function makeInsertOptions( $options ) {
01597                 return implode( ' ', $options );
01598         }
01599 
01633         public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
01634                 # No rows to insert, easy just return now
01635                 if ( !count( $a ) ) {
01636                         return true;
01637                 }
01638 
01639                 $table = $this->tableName( $table );
01640 
01641                 if ( !is_array( $options ) ) {
01642                         $options = array( $options );
01643                 }
01644 
01645                 $options = $this->makeInsertOptions( $options );
01646 
01647                 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01648                         $multi = true;
01649                         $keys = array_keys( $a[0] );
01650                 } else {
01651                         $multi = false;
01652                         $keys = array_keys( $a );
01653                 }
01654 
01655                 $sql = 'INSERT ' . $options .
01656                         " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01657 
01658                 if ( $multi ) {
01659                         $first = true;
01660                         foreach ( $a as $row ) {
01661                                 if ( $first ) {
01662                                         $first = false;
01663                                 } else {
01664                                         $sql .= ',';
01665                                 }
01666                                 $sql .= '(' . $this->makeList( $row ) . ')';
01667                         }
01668                 } else {
01669                         $sql .= '(' . $this->makeList( $a ) . ')';
01670                 }
01671 
01672                 return (bool)$this->query( $sql, $fname );
01673         }
01674 
01681         protected function makeUpdateOptions( $options ) {
01682                 if ( !is_array( $options ) ) {
01683                         $options = array( $options );
01684                 }
01685 
01686                 $opts = array();
01687 
01688                 if ( in_array( 'LOW_PRIORITY', $options ) ) {
01689                         $opts[] = $this->lowPriorityOption();
01690                 }
01691 
01692                 if ( in_array( 'IGNORE', $options ) ) {
01693                         $opts[] = 'IGNORE';
01694                 }
01695 
01696                 return implode( ' ', $opts );
01697         }
01698 
01722         function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
01723                 $table = $this->tableName( $table );
01724                 $opts = $this->makeUpdateOptions( $options );
01725                 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01726 
01727                 if ( $conds !== array() && $conds !== '*' ) {
01728                         $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01729                 }
01730 
01731                 return $this->query( $sql, $fname );
01732         }
01733 
01747         public function makeList( $a, $mode = LIST_COMMA ) {
01748                 if ( !is_array( $a ) ) {
01749                         throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
01750                 }
01751 
01752                 $first = true;
01753                 $list = '';
01754 
01755                 foreach ( $a as $field => $value ) {
01756                         if ( !$first ) {
01757                                 if ( $mode == LIST_AND ) {
01758                                         $list .= ' AND ';
01759                                 } elseif ( $mode == LIST_OR ) {
01760                                         $list .= ' OR ';
01761                                 } else {
01762                                         $list .= ',';
01763                                 }
01764                         } else {
01765                                 $first = false;
01766                         }
01767 
01768                         if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
01769                                 $list .= "($value)";
01770                         } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
01771                                 $list .= "$value";
01772                         } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
01773                                 if ( count( $value ) == 0 ) {
01774                                         throw new MWException( __METHOD__ . ': empty input' );
01775                                 } elseif ( count( $value ) == 1 ) {
01776                                         // Special-case single values, as IN isn't terribly efficient
01777                                         // Don't necessarily assume the single key is 0; we don't
01778                                         // enforce linear numeric ordering on other arrays here.
01779                                         $value = array_values( $value );
01780                                         $list .= $field . " = " . $this->addQuotes( $value[0] );
01781                                 } else {
01782                                         $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
01783                                 }
01784                         } elseif ( $value === null ) {
01785                                 if ( $mode == LIST_AND || $mode == LIST_OR ) {
01786                                         $list .= "$field IS ";
01787                                 } elseif ( $mode == LIST_SET ) {
01788                                         $list .= "$field = ";
01789                                 }
01790                                 $list .= 'NULL';
01791                         } else {
01792                                 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
01793                                         $list .= "$field = ";
01794                                 }
01795                                 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
01796                         }
01797                 }
01798 
01799                 return $list;
01800         }
01801 
01812         public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
01813                 $conds = array();
01814 
01815                 foreach ( $data as $base => $sub ) {
01816                         if ( count( $sub ) ) {
01817                                 $conds[] = $this->makeList(
01818                                         array( $baseKey => $base, $subKey => array_keys( $sub ) ),
01819                                         LIST_AND );
01820                         }
01821                 }
01822 
01823                 if ( $conds ) {
01824                         return $this->makeList( $conds, LIST_OR );
01825                 } else {
01826                         // Nothing to search for...
01827                         return false;
01828                 }
01829         }
01830 
01839         public function aggregateValue( $valuedata, $valuename = 'value' ) {
01840                 return $valuename;
01841         }
01842 
01847         public function bitNot( $field ) {
01848                 return "(~$field)";
01849         }
01850 
01856         public function bitAnd( $fieldLeft, $fieldRight ) {
01857                 return "($fieldLeft & $fieldRight)";
01858         }
01859 
01865         public function bitOr( $fieldLeft, $fieldRight ) {
01866                 return "($fieldLeft | $fieldRight)";
01867         }
01868 
01874         public function buildConcat( $stringList ) {
01875                 return 'CONCAT(' . implode( ',', $stringList ) . ')';
01876         }
01877 
01887         public function selectDB( $db ) {
01888                 # Stub.  Shouldn't cause serious problems if it's not overridden, but
01889                 # if your database engine supports a concept similar to MySQL's
01890                 # databases you may as well.
01891                 $this->mDBname = $db;
01892                 return true;
01893         }
01894 
01898         public function getDBname() {
01899                 return $this->mDBname;
01900         }
01901 
01905         public function getServer() {
01906                 return $this->mServer;
01907         }
01908 
01926         public function tableName( $name, $format = 'quoted' ) {
01927                 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
01928                 # Skip the entire process when we have a string quoted on both ends.
01929                 # Note that we check the end so that we will still quote any use of
01930                 # use of `database`.table. But won't break things if someone wants
01931                 # to query a database table with a dot in the name.
01932                 if ( $this->isQuotedIdentifier( $name ) ) {
01933                         return $name;
01934                 }
01935 
01936                 # Lets test for any bits of text that should never show up in a table
01937                 # name. Basically anything like JOIN or ON which are actually part of
01938                 # SQL queries, but may end up inside of the table value to combine
01939                 # sql. Such as how the API is doing.
01940                 # Note that we use a whitespace test rather than a \b test to avoid
01941                 # any remote case where a word like on may be inside of a table name
01942                 # surrounded by symbols which may be considered word breaks.
01943                 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
01944                         return $name;
01945                 }
01946 
01947                 # Split database and table into proper variables.
01948                 # We reverse the explode so that database.table and table both output
01949                 # the correct table.
01950                 $dbDetails = array_reverse( explode( '.', $name, 2 ) );
01951                 if ( isset( $dbDetails[1] ) ) {
01952                         list( $table, $database ) = $dbDetails;
01953                 } else {
01954                         list( $table ) = $dbDetails;
01955                 }
01956                 $prefix = $this->mTablePrefix; # Default prefix
01957 
01958                 # A database name has been specified in input. We don't want any
01959                 # prefixes added.
01960                 if ( isset( $database ) ) {
01961                         $prefix = '';
01962                 }
01963 
01964                 # Note that we use the long format because php will complain in in_array if
01965                 # the input is not an array, and will complain in is_array if it is not set.
01966                 if ( !isset( $database ) # Don't use shared database if pre selected.
01967                  && isset( $wgSharedDB ) # We have a shared database
01968                  && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
01969                  && isset( $wgSharedTables )
01970                  && is_array( $wgSharedTables )
01971                  && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
01972                         $database = $wgSharedDB;
01973                         $prefix   = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
01974                 }
01975 
01976                 # Quote the $database and $table and apply the prefix if not quoted.
01977                 if ( isset( $database ) ) {
01978                         if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
01979                                 $database = $this->addIdentifierQuotes( $database );
01980                         }
01981                 }
01982 
01983                 $table = "{$prefix}{$table}";
01984                 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) {
01985                         $table = $this->addIdentifierQuotes( "{$table}" );
01986                 }
01987 
01988                 # Merge our database and table into our final table name.
01989                 $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
01990 
01991                 return $tableName;
01992         }
01993 
02005         public function tableNames() {
02006                 $inArray = func_get_args();
02007                 $retVal = array();
02008 
02009                 foreach ( $inArray as $name ) {
02010                         $retVal[$name] = $this->tableName( $name );
02011                 }
02012 
02013                 return $retVal;
02014         }
02015 
02027         public function tableNamesN() {
02028                 $inArray = func_get_args();
02029                 $retVal = array();
02030 
02031                 foreach ( $inArray as $name ) {
02032                         $retVal[] = $this->tableName( $name );
02033                 }
02034 
02035                 return $retVal;
02036         }
02037 
02046         public function tableNameWithAlias( $name, $alias = false ) {
02047                 if ( !$alias || $alias == $name ) {
02048                         return $this->tableName( $name );
02049                 } else {
02050                         return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
02051                 }
02052         }
02053 
02060         public function tableNamesWithAlias( $tables ) {
02061                 $retval = array();
02062                 foreach ( $tables as $alias => $table ) {
02063                         if ( is_numeric( $alias ) ) {
02064                                 $alias = $table;
02065                         }
02066                         $retval[] = $this->tableNameWithAlias( $table, $alias );
02067                 }
02068                 return $retval;
02069         }
02070 
02079         public function fieldNameWithAlias( $name, $alias = false ) {
02080                 if ( !$alias || (string)$alias === (string)$name ) {
02081                         return $name;
02082                 } else {
02083                         return $name . ' AS ' . $alias; //PostgreSQL needs AS
02084                 }
02085         }
02086 
02093         public function fieldNamesWithAlias( $fields ) {
02094                 $retval = array();
02095                 foreach ( $fields as $alias => $field ) {
02096                         if ( is_numeric( $alias ) ) {
02097                                 $alias = $field;
02098                         }
02099                         $retval[] = $this->fieldNameWithAlias( $field, $alias );
02100                 }
02101                 return $retval;
02102         }
02103 
02113         protected function tableNamesWithUseIndexOrJOIN(
02114                 $tables, $use_index = array(), $join_conds = array()
02115         ) {
02116                 $ret = array();
02117                 $retJOIN = array();
02118                 $use_index = (array)$use_index;
02119                 $join_conds = (array)$join_conds;
02120 
02121                 foreach ( $tables as $alias => $table ) {
02122                         if ( !is_string( $alias ) ) {
02123                                 // No alias? Set it equal to the table name
02124                                 $alias = $table;
02125                         }
02126                         // Is there a JOIN clause for this table?
02127                         if ( isset( $join_conds[$alias] ) ) {
02128                                 list( $joinType, $conds ) = $join_conds[$alias];
02129                                 $tableClause = $joinType;
02130                                 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
02131                                 if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
02132                                         $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
02133                                         if ( $use != '' ) {
02134                                                 $tableClause .= ' ' . $use;
02135                                         }
02136                                 }
02137                                 $on = $this->makeList( (array)$conds, LIST_AND );
02138                                 if ( $on != '' ) {
02139                                         $tableClause .= ' ON (' . $on . ')';
02140                                 }
02141 
02142                                 $retJOIN[] = $tableClause;
02143                         // Is there an INDEX clause for this table?
02144                         } elseif ( isset( $use_index[$alias] ) ) {
02145                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02146                                 $tableClause .= ' ' . $this->useIndexClause(
02147                                         implode( ',', (array)$use_index[$alias] ) );
02148 
02149                                 $ret[] = $tableClause;
02150                         } else {
02151                                 $tableClause = $this->tableNameWithAlias( $table, $alias );
02152 
02153                                 $ret[] = $tableClause;
02154                         }
02155                 }
02156 
02157                 // We can't separate explicit JOIN clauses with ',', use ' ' for those
02158                 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
02159                 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
02160 
02161                 // Compile our final table clause
02162                 return implode( ' ', array( $straightJoins, $otherJoins ) );
02163         }
02164 
02172         protected function indexName( $index ) {
02173                 // Backwards-compatibility hack
02174                 $renamed = array(
02175                         'ar_usertext_timestamp' => 'usertext_timestamp',
02176                         'un_user_id'            => 'user_id',
02177                         'un_user_ip'            => 'user_ip',
02178                 );
02179 
02180                 if ( isset( $renamed[$index] ) ) {
02181                         return $renamed[$index];
02182                 } else {
02183                         return $index;
02184                 }
02185         }
02186 
02195         public function addQuotes( $s ) {
02196                 if ( $s === null ) {
02197                         return 'NULL';
02198                 } else {
02199                         # This will also quote numeric values. This should be harmless,
02200                         # and protects against weird problems that occur when they really
02201                         # _are_ strings such as article titles and string->number->string
02202                         # conversion is not 1:1.
02203                         return "'" . $this->strencode( $s ) . "'";
02204                 }
02205         }
02206 
02217         public function addIdentifierQuotes( $s ) {
02218                 return '"' . str_replace( '"', '""', $s ) . '"';
02219         }
02220 
02229         public function isQuotedIdentifier( $name ) {
02230                 return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
02231         }
02232 
02237         protected function escapeLikeInternal( $s ) {
02238                 $s = str_replace( '\\', '\\\\', $s );
02239                 $s = $this->strencode( $s );
02240                 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
02241 
02242                 return $s;
02243         }
02244 
02257         public function buildLike() {
02258                 $params = func_get_args();
02259 
02260                 if ( count( $params ) > 0 && is_array( $params[0] ) ) {
02261                         $params = $params[0];
02262                 }
02263 
02264                 $s = '';
02265 
02266                 foreach ( $params as $value ) {
02267                         if ( $value instanceof LikeMatch ) {
02268                                 $s .= $value->toString();
02269                         } else {
02270                                 $s .= $this->escapeLikeInternal( $value );
02271                         }
02272                 }
02273 
02274                 return " LIKE '" . $s . "' ";
02275         }
02276 
02282         public function anyChar() {
02283                 return new LikeMatch( '_' );
02284         }
02285 
02291         public function anyString() {
02292                 return new LikeMatch( '%' );
02293         }
02294 
02306         public function nextSequenceValue( $seqName ) {
02307                 return null;
02308         }
02309 
02320         public function useIndexClause( $index ) {
02321                 return '';
02322         }
02323 
02346         public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
02347                 $quotedTable = $this->tableName( $table );
02348 
02349                 if ( count( $rows ) == 0 ) {
02350                         return;
02351                 }
02352 
02353                 # Single row case
02354                 if ( !is_array( reset( $rows ) ) ) {
02355                         $rows = array( $rows );
02356                 }
02357 
02358                 foreach( $rows as $row ) {
02359                         # Delete rows which collide
02360                         if ( $uniqueIndexes ) {
02361                                 $sql = "DELETE FROM $quotedTable WHERE ";
02362                                 $first = true;
02363                                 foreach ( $uniqueIndexes as $index ) {
02364                                         if ( $first ) {
02365                                                 $first = false;
02366                                                 $sql .= '( ';
02367                                         } else {
02368                                                 $sql .= ' ) OR ( ';
02369                                         }
02370                                         if ( is_array( $index ) ) {
02371                                                 $first2 = true;
02372                                                 foreach ( $index as $col ) {
02373                                                         if ( $first2 ) {
02374                                                                 $first2 = false;
02375                                                         } else {
02376                                                                 $sql .= ' AND ';
02377                                                         }
02378                                                         $sql .= $col . '=' . $this->addQuotes( $row[$col] );
02379                                                 }
02380                                         } else {
02381                                                 $sql .= $index . '=' . $this->addQuotes( $row[$index] );
02382                                         }
02383                                 }
02384                                 $sql .= ' )';
02385                                 $this->query( $sql, $fname );
02386                         }
02387 
02388                         # Now insert the row
02389                         $this->insert( $table, $row );
02390                 }
02391         }
02392 
02403         protected function nativeReplace( $table, $rows, $fname ) {
02404                 $table = $this->tableName( $table );
02405 
02406                 # Single row case
02407                 if ( !is_array( reset( $rows ) ) ) {
02408                         $rows = array( $rows );
02409                 }
02410 
02411                 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
02412                 $first = true;
02413 
02414                 foreach ( $rows as $row ) {
02415                         if ( $first ) {
02416                                 $first = false;
02417                         } else {
02418                                 $sql .= ',';
02419                         }
02420 
02421                         $sql .= '(' . $this->makeList( $row ) . ')';
02422                 }
02423 
02424                 return $this->query( $sql, $fname );
02425         }
02426 
02447         public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
02448                 $fname = 'DatabaseBase::deleteJoin' )
02449         {
02450                 if ( !$conds ) {
02451                         throw new DBUnexpectedError( $this,
02452                                 'DatabaseBase::deleteJoin() called with empty $conds' );
02453                 }
02454 
02455                 $delTable = $this->tableName( $delTable );
02456                 $joinTable = $this->tableName( $joinTable );
02457                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
02458                 if ( $conds != '*' ) {
02459                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
02460                 }
02461                 $sql .= ')';
02462 
02463                 $this->query( $sql, $fname );
02464         }
02465 
02474         public function textFieldSize( $table, $field ) {
02475                 $table = $this->tableName( $table );
02476                 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
02477                 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
02478                 $row = $this->fetchObject( $res );
02479 
02480                 $m = array();
02481 
02482                 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
02483                         $size = $m[1];
02484                 } else {
02485                         $size = -1;
02486                 }
02487 
02488                 return $size;
02489         }
02490 
02499         public function lowPriorityOption() {
02500                 return '';
02501         }
02502 
02513         public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
02514                 if ( !$conds ) {
02515                         throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
02516                 }
02517 
02518                 $table = $this->tableName( $table );
02519                 $sql = "DELETE FROM $table";
02520 
02521                 if ( $conds != '*' ) {
02522                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
02523                 }
02524 
02525                 return $this->query( $sql, $fname );
02526         }
02527 
02554         public function insertSelect( $destTable, $srcTable, $varMap, $conds,
02555                 $fname = 'DatabaseBase::insertSelect',
02556                 $insertOptions = array(), $selectOptions = array() )
02557         {
02558                 $destTable = $this->tableName( $destTable );
02559 
02560                 if ( is_array( $insertOptions ) ) {
02561                         $insertOptions = implode( ' ', $insertOptions );
02562                 }
02563 
02564                 if ( !is_array( $selectOptions ) ) {
02565                         $selectOptions = array( $selectOptions );
02566                 }
02567 
02568                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
02569 
02570                 if ( is_array( $srcTable ) ) {
02571                         $srcTable =  implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
02572                 } else {
02573                         $srcTable = $this->tableName( $srcTable );
02574                 }
02575 
02576                 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
02577                         " SELECT $startOpts " . implode( ',', $varMap ) .
02578                         " FROM $srcTable $useIndex ";
02579 
02580                 if ( $conds != '*' ) {
02581                         if ( is_array( $conds ) ) {
02582                                 $conds = $this->makeList( $conds, LIST_AND );
02583                         }
02584                         $sql .= " WHERE $conds";
02585                 }
02586 
02587                 $sql .= " $tailOpts";
02588 
02589                 return $this->query( $sql, $fname );
02590         }
02591 
02611         public function limitResult( $sql, $limit, $offset = false ) {
02612                 if ( !is_numeric( $limit ) ) {
02613                         throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
02614                 }
02615                 return "$sql LIMIT "
02616                         . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
02617                         . "{$limit} ";
02618         }
02619 
02625         public function unionSupportsOrderAndLimit() {
02626                 return true; // True for almost every DB supported
02627         }
02628 
02637         public function unionQueries( $sqls, $all ) {
02638                 $glue = $all ? ') UNION ALL (' : ') UNION (';
02639                 return '(' . implode( $glue, $sqls ) . ')';
02640         }
02641 
02651         public function conditional( $cond, $trueVal, $falseVal ) {
02652                 if ( is_array( $cond ) ) {
02653                         $cond = $this->makeList( $cond, LIST_AND );
02654                 }
02655                 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
02656         }
02657 
02668         public function strreplace( $orig, $old, $new ) {
02669                 return "REPLACE({$orig}, {$old}, {$new})";
02670         }
02671 
02678         public function getServerUptime() {
02679                 return 0;
02680         }
02681 
02688         public function wasDeadlock() {
02689                 return false;
02690         }
02691 
02698         public function wasLockTimeout() {
02699                 return false;
02700         }
02701 
02709         public function wasErrorReissuable() {
02710                 return false;
02711         }
02712 
02719         public function wasReadOnlyError() {
02720                 return false;
02721         }
02722 
02741         public function deadlockLoop() {
02742                 $this->begin( __METHOD__ );
02743                 $args = func_get_args();
02744                 $function = array_shift( $args );
02745                 $oldIgnore = $this->ignoreErrors( true );
02746                 $tries = DEADLOCK_TRIES;
02747 
02748                 if ( is_array( $function ) ) {
02749                         $fname = $function[0];
02750                 } else {
02751                         $fname = $function;
02752                 }
02753 
02754                 do {
02755                         $retVal = call_user_func_array( $function, $args );
02756                         $error = $this->lastError();
02757                         $errno = $this->lastErrno();
02758                         $sql = $this->lastQuery();
02759 
02760                         if ( $errno ) {
02761                                 if ( $this->wasDeadlock() ) {
02762                                         # Retry
02763                                         usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
02764                                 } else {
02765                                         $this->reportQueryError( $error, $errno, $sql, $fname );
02766                                 }
02767                         }
02768                 } while ( $this->wasDeadlock() && --$tries > 0 );
02769 
02770                 $this->ignoreErrors( $oldIgnore );
02771 
02772                 if ( $tries <= 0 ) {
02773                         $this->rollback( __METHOD__ );
02774                         $this->reportQueryError( $error, $errno, $sql, $fname );
02775                         return false;
02776                 } else {
02777                         $this->commit( __METHOD__ );
02778                         return $retVal;
02779                 }
02780         }
02781 
02793         public function masterPosWait( DBMasterPos $pos, $timeout ) {
02794                 wfProfileIn( __METHOD__ );
02795 
02796                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02797                         $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
02798 
02799                         if ( $wait > $timeout * 1e6 ) {
02800                                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
02801                                 wfProfileOut( __METHOD__ );
02802                                 return -1;
02803                         } elseif ( $wait > 0 ) {
02804                                 wfDebug( "Fake slave waiting $wait us\n" );
02805                                 usleep( $wait );
02806                                 wfProfileOut( __METHOD__ );
02807                                 return 1;
02808                         } else {
02809                                 wfDebug( "Fake slave up to date ($wait us)\n" );
02810                                 wfProfileOut( __METHOD__ );
02811                                 return 0;
02812                         }
02813                 }
02814 
02815                 wfProfileOut( __METHOD__ );
02816 
02817                 # Real waits are implemented in the subclass.
02818                 return 0;
02819         }
02820 
02826         public function getSlavePos() {
02827                 if ( !is_null( $this->mFakeSlaveLag ) ) {
02828                         $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
02829                         wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
02830                         return $pos;
02831                 } else {
02832                         # Stub
02833                         return false;
02834                 }
02835         }
02836 
02842         public function getMasterPos() {
02843                 if ( $this->mFakeMaster ) {
02844                         return new MySQLMasterPos( 'fake', microtime( true ) );
02845                 } else {
02846                         return false;
02847                 }
02848         }
02849 
02860         final public function onTransactionIdle( Closure $callback ) {
02861                 if ( $this->mTrxLevel ) {
02862                         $this->mTrxIdleCallbacks[] = $callback;
02863                 } else {
02864                         $callback();
02865                 }
02866         }
02867 
02871         protected function runOnTransactionIdleCallbacks() {
02872                 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
02873 
02874                 $e = null; // last exception
02875                 do { // callbacks may add callbacks :)
02876                         $callbacks = $this->mTrxIdleCallbacks;
02877                         $this->mTrxIdleCallbacks = array(); // recursion guard
02878                         foreach ( $callbacks as $callback ) {
02879                                 try {
02880                                         $this->clearFlag( DBO_TRX ); // make each query its own transaction
02881                                         $callback();
02882                                         $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
02883                                 } catch ( Exception $e ) {}
02884                         }
02885                 } while ( count( $this->mTrxIdleCallbacks ) );
02886 
02887                 if ( $e instanceof Exception ) {
02888                         throw $e; // re-throw any last exception
02889                 }
02890         }
02891 
02897         final public function begin( $fname = 'DatabaseBase::begin' ) {
02898                 if ( $this->mTrxLevel ) { // implicit commit
02899                         $this->doCommit( $fname );
02900                         $this->runOnTransactionIdleCallbacks();
02901                 }
02902                 $this->doBegin( $fname );
02903         }
02904 
02909         protected function doBegin( $fname ) {
02910                 $this->query( 'BEGIN', $fname );
02911                 $this->mTrxLevel = 1;
02912         }
02913 
02919         final public function commit( $fname = 'DatabaseBase::commit' ) {
02920                 $this->doCommit( $fname );
02921                 $this->runOnTransactionIdleCallbacks();
02922         }
02923 
02928         protected function doCommit( $fname ) {
02929                 if ( $this->mTrxLevel ) {
02930                         $this->query( 'COMMIT', $fname );
02931                         $this->mTrxLevel = 0;
02932                 }
02933         }
02934 
02941         final public function rollback( $fname = 'DatabaseBase::rollback' ) {
02942                 $this->doRollback( $fname );
02943                 $this->mTrxIdleCallbacks = array(); // cancel
02944         }
02945 
02950         protected function doRollback( $fname ) {
02951                 if ( $this->mTrxLevel ) {
02952                         $this->query( 'ROLLBACK', $fname, true );
02953                         $this->mTrxLevel = 0;
02954                 }
02955         }
02956 
02971         public function duplicateTableStructure( $oldName, $newName, $temporary = false,
02972                 $fname = 'DatabaseBase::duplicateTableStructure' )
02973         {
02974                 throw new MWException(
02975                         'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
02976         }
02977 
02984         function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
02985                 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
02986         }
02987 
02999         public function timestamp( $ts = 0 ) {
03000                 return wfTimestamp( TS_MW, $ts );
03001         }
03002 
03016         public function timestampOrNull( $ts = null ) {
03017                 if ( is_null( $ts ) ) {
03018                         return null;
03019                 } else {
03020                         return $this->timestamp( $ts );
03021                 }
03022         }
03023 
03039         public function resultObject( $result ) {
03040                 if ( empty( $result ) ) {
03041                         return false;
03042                 } elseif ( $result instanceof ResultWrapper ) {
03043                         return $result;
03044                 } elseif ( $result === true ) {
03045                         // Successful write query
03046                         return $result;
03047                 } else {
03048                         return new ResultWrapper( $this, $result );
03049                 }
03050         }
03051 
03057         public function ping() {
03058                 # Stub.  Not essential to override.
03059                 return true;
03060         }
03061 
03071         public function getLag() {
03072                 return intval( $this->mFakeSlaveLag );
03073         }
03074 
03080         function maxListLen() {
03081                 return 0;
03082         }
03083 
03092         public function encodeBlob( $b ) {
03093                 return $b;
03094         }
03095 
03103         public function decodeBlob( $b ) {
03104                 return $b;
03105         }
03106 
03117         public function setSessionOptions( array $options ) {}
03118 
03132         public function sourceFile(
03133                 $filename, $lineCallback = false, $resultCallback = false, $fname = false
03134         ) {
03135                 wfSuppressWarnings();
03136                 $fp = fopen( $filename, 'r' );
03137                 wfRestoreWarnings();
03138 
03139                 if ( false === $fp ) {
03140                         throw new MWException( "Could not open \"{$filename}\".\n" );
03141                 }
03142 
03143                 if ( !$fname ) {
03144                         $fname = __METHOD__ . "( $filename )";
03145                 }
03146 
03147                 try {
03148                         $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
03149                 }
03150                 catch ( MWException $e ) {
03151                         fclose( $fp );
03152                         throw $e;
03153                 }
03154 
03155                 fclose( $fp );
03156 
03157                 return $error;
03158         }
03159 
03168         public function patchPath( $patch ) {
03169                 global $IP;
03170 
03171                 $dbType = $this->getType();
03172                 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
03173                         return "$IP/maintenance/$dbType/archives/$patch";
03174                 } else {
03175                         return "$IP/maintenance/archives/$patch";
03176                 }
03177         }
03178 
03186         public function setSchemaVars( $vars ) {
03187                 $this->mSchemaVars = $vars;
03188         }
03189 
03203         public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
03204                 $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
03205         {
03206                 $cmd = '';
03207 
03208                 while ( !feof( $fp ) ) {
03209                         if ( $lineCallback ) {
03210                                 call_user_func( $lineCallback );
03211                         }
03212 
03213                         $line = trim( fgets( $fp ) );
03214 
03215                         if ( $line == '' ) {
03216                                 continue;
03217                         }
03218 
03219                         if ( '-' == $line[0] && '-' == $line[1] ) {
03220                                 continue;
03221                         }
03222 
03223                         if ( $cmd != '' ) {
03224                                 $cmd .= ' ';
03225                         }
03226 
03227                         $done = $this->streamStatementEnd( $cmd, $line );
03228 
03229                         $cmd .= "$line\n";
03230 
03231                         if ( $done || feof( $fp ) ) {
03232                                 $cmd = $this->replaceVars( $cmd );
03233                                 if ( $inputCallback ) {
03234                                         call_user_func( $inputCallback, $cmd );
03235                                 }
03236                                 $res = $this->query( $cmd, $fname );
03237 
03238                                 if ( $resultCallback ) {
03239                                         call_user_func( $resultCallback, $res, $this );
03240                                 }
03241 
03242                                 if ( false === $res ) {
03243                                         $err = $this->lastError();
03244                                         return "Query \"{$cmd}\" failed with error code \"$err\".\n";
03245                                 }
03246 
03247                                 $cmd = '';
03248                         }
03249                 }
03250 
03251                 return true;
03252         }
03253 
03261         public function streamStatementEnd( &$sql, &$newLine ) {
03262                 if ( $this->delimiter ) {
03263                         $prev = $newLine;
03264                         $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
03265                         if ( $newLine != $prev ) {
03266                                 return true;
03267                         }
03268                 }
03269                 return false;
03270         }
03271 
03289         protected function replaceSchemaVars( $ins ) {
03290                 $vars = $this->getSchemaVars();
03291                 foreach ( $vars as $var => $value ) {
03292                         // replace '{$var}'
03293                         $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
03294                         // replace `{$var}`
03295                         $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
03296                         // replace /*$var*/
03297                         $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins );
03298                 }
03299                 return $ins;
03300         }
03301 
03309         protected function replaceVars( $ins ) {
03310                 $ins = $this->replaceSchemaVars( $ins );
03311 
03312                 // Table prefixes
03313                 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
03314                         array( $this, 'tableNameCallback' ), $ins );
03315 
03316                 // Index names
03317                 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
03318                         array( $this, 'indexNameCallback' ), $ins );
03319 
03320                 return $ins;
03321         }
03322 
03329         protected function getSchemaVars() {
03330                 if ( $this->mSchemaVars ) {
03331                         return $this->mSchemaVars;
03332                 } else {
03333                         return $this->getDefaultSchemaVars();
03334                 }
03335         }
03336 
03345         protected function getDefaultSchemaVars() {
03346                 return array();
03347         }
03348 
03356         protected function tableNameCallback( $matches ) {
03357                 return $this->tableName( $matches[1] );
03358         }
03359 
03367         protected function indexNameCallback( $matches ) {
03368                 return $this->indexName( $matches[1] );
03369         }
03370 
03379         public function lockIsFree( $lockName, $method ) {
03380                 return true;
03381         }
03382 
03394         public function lock( $lockName, $method, $timeout = 5 ) {
03395                 return true;
03396         }
03397 
03408         public function unlock( $lockName, $method ) {
03409                 return true;
03410         }
03411 
03422         public function lockTables( $read, $write, $method, $lowPriority = true ) {
03423                 return true;
03424         }
03425 
03433         public function unlockTables( $method ) {
03434                 return true;
03435         }
03436 
03444         public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
03445                 if( !$this->tableExists( $tableName, $fName ) ) {
03446                         return false;
03447                 }
03448                 $sql = "DROP TABLE " . $this->tableName( $tableName );
03449                 if( $this->cascadingDeletes() ) {
03450                         $sql .= " CASCADE";
03451                 }
03452                 return $this->query( $sql, $fName );
03453         }
03454 
03461         public function getSearchEngine() {
03462                 return 'SearchEngineDummy';
03463         }
03464 
03472         public function getInfinity() {
03473                 return 'infinity';
03474         }
03475 
03482         public function encodeExpiry( $expiry ) {
03483                 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
03484                         ? $this->getInfinity()
03485                         : $this->timestamp( $expiry );
03486         }
03487 
03495         public function decodeExpiry( $expiry, $format = TS_MW ) {
03496                 return ( $expiry == '' || $expiry == $this->getInfinity() )
03497                         ? 'infinity'
03498                         : wfTimestamp( $format, $expiry );
03499         }
03500 
03510         public function setBigSelects( $value = true ) {
03511                 // no-op
03512         }
03513 
03517         public function __toString() {
03518                 return (string)$this->mConn;
03519         }
03520 
03521         public function __destruct() {
03522                 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
03523                         trigger_error( "Transaction idle callbacks still pending." );
03524                 }
03525         }
03526 }