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