MediaWiki
REL1_21
|
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 00071 function fetchObject( $res ); 00072 00082 function fetchRow( $res ); 00083 00090 function numRows( $res ); 00091 00099 function numFields( $res ); 00100 00109 function fieldName( $res, $n ); 00110 00123 function insertId(); 00124 00132 function dataSeek( $res, $row ); 00133 00140 function lastErrno(); 00141 00148 function lastError(); 00149 00159 function fieldInfo( $table, $field ); 00160 00168 function indexInfo( $table, $index, $fname = 'Database::indexInfo' ); 00169 00176 function affectedRows(); 00177 00184 function strencode( $s ); 00185 00194 static function getSoftwareLink(); 00195 00202 function getServerVersion(); 00203 00211 function getServerInfo(); 00212 } 00213 00218 abstract class DatabaseBase implements DatabaseType { 00219 00220 # ------------------------------------------------------------------------------ 00221 # Variables 00222 # ------------------------------------------------------------------------------ 00223 00224 protected $mLastQuery = ''; 00225 protected $mDoneWrites = false; 00226 protected $mPHPError = false; 00227 00228 protected $mServer, $mUser, $mPassword, $mDBname; 00229 00230 protected $mConn = null; 00231 protected $mOpened = false; 00232 00237 protected $mTrxIdleCallbacks = array(); 00238 00239 protected $mTablePrefix; 00240 protected $mFlags; 00241 protected $mTrxLevel = 0; 00242 protected $mErrorCount = 0; 00243 protected $mLBInfo = array(); 00244 protected $mFakeSlaveLag = null, $mFakeMaster = false; 00245 protected $mDefaultBigSelects = null; 00246 protected $mSchemaVars = false; 00247 00248 protected $preparedArgs; 00249 00250 protected $htmlErrors; 00251 00252 protected $delimiter = ';'; 00253 00261 private $mTrxFname = null; 00262 00269 private $mTrxDoneWrites = false; 00270 00277 private $mTrxAutomatic = false; 00278 00283 protected $fileHandle = null; 00284 00285 # ------------------------------------------------------------------------------ 00286 # Accessors 00287 # ------------------------------------------------------------------------------ 00288 # These optionally set a variable and return the previous state 00289 00297 public function getServerInfo() { 00298 return $this->getServerVersion(); 00299 } 00300 00304 public function getDelimiter() { 00305 return $this->delimiter; 00306 } 00307 00317 public function debug( $debug = null ) { 00318 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 00319 } 00320 00343 public function bufferResults( $buffer = null ) { 00344 if ( is_null( $buffer ) ) { 00345 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 00346 } else { 00347 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 00348 } 00349 } 00350 00362 public function ignoreErrors( $ignoreErrors = null ) { 00363 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 00364 } 00365 00375 public function trxLevel( $level = null ) { 00376 return wfSetVar( $this->mTrxLevel, $level ); 00377 } 00378 00384 public function errorCount( $count = null ) { 00385 return wfSetVar( $this->mErrorCount, $count ); 00386 } 00387 00393 public function tablePrefix( $prefix = null ) { 00394 return wfSetVar( $this->mTablePrefix, $prefix ); 00395 } 00396 00402 public function setFileHandle( $fh ) { 00403 $this->fileHandle = $fh; 00404 } 00405 00415 public function getLBInfo( $name = null ) { 00416 if ( is_null( $name ) ) { 00417 return $this->mLBInfo; 00418 } else { 00419 if ( array_key_exists( $name, $this->mLBInfo ) ) { 00420 return $this->mLBInfo[$name]; 00421 } else { 00422 return null; 00423 } 00424 } 00425 } 00426 00435 public function setLBInfo( $name, $value = null ) { 00436 if ( is_null( $value ) ) { 00437 $this->mLBInfo = $name; 00438 } else { 00439 $this->mLBInfo[$name] = $value; 00440 } 00441 } 00442 00448 public function setFakeSlaveLag( $lag ) { 00449 $this->mFakeSlaveLag = $lag; 00450 } 00451 00457 public function setFakeMaster( $enabled = true ) { 00458 $this->mFakeMaster = $enabled; 00459 } 00460 00466 public function cascadingDeletes() { 00467 return false; 00468 } 00469 00475 public function cleanupTriggers() { 00476 return false; 00477 } 00478 00485 public function strictIPs() { 00486 return false; 00487 } 00488 00494 public function realTimestamps() { 00495 return false; 00496 } 00497 00503 public function implicitGroupby() { 00504 return true; 00505 } 00506 00513 public function implicitOrderby() { 00514 return true; 00515 } 00516 00523 public function searchableIPs() { 00524 return false; 00525 } 00526 00532 public function functionalIndexes() { 00533 return false; 00534 } 00535 00540 public function lastQuery() { 00541 return $this->mLastQuery; 00542 } 00543 00550 public function doneWrites() { 00551 return $this->mDoneWrites; 00552 } 00553 00560 public function writesOrCallbacksPending() { 00561 return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks ); 00562 } 00563 00568 public function isOpen() { 00569 return $this->mOpened; 00570 } 00571 00584 public function setFlag( $flag ) { 00585 global $wgDebugDBTransactions; 00586 $this->mFlags |= $flag; 00587 if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) { 00588 wfDebug( "Implicit transactions are now disabled.\n" ); 00589 } 00590 } 00591 00597 public function clearFlag( $flag ) { 00598 global $wgDebugDBTransactions; 00599 $this->mFlags &= ~$flag; 00600 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00601 wfDebug( "Implicit transactions are now disabled.\n" ); 00602 } 00603 } 00604 00611 public function getFlag( $flag ) { 00612 return !!( $this->mFlags & $flag ); 00613 } 00614 00622 public function getProperty( $name ) { 00623 return $this->$name; 00624 } 00625 00629 public function getWikiID() { 00630 if ( $this->mTablePrefix ) { 00631 return "{$this->mDBname}-{$this->mTablePrefix}"; 00632 } else { 00633 return $this->mDBname; 00634 } 00635 } 00636 00642 public function getSchemaPath() { 00643 global $IP; 00644 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) { 00645 return "$IP/maintenance/" . $this->getType() . "/tables.sql"; 00646 } else { 00647 return "$IP/maintenance/tables.sql"; 00648 } 00649 } 00650 00651 # ------------------------------------------------------------------------------ 00652 # Other functions 00653 # ------------------------------------------------------------------------------ 00654 00664 function __construct( $server = false, $user = false, $password = false, $dbName = false, 00665 $flags = 0, $tablePrefix = 'get from global' 00666 ) { 00667 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions; 00668 00669 $this->mFlags = $flags; 00670 00671 if ( $this->mFlags & DBO_DEFAULT ) { 00672 if ( $wgCommandLineMode ) { 00673 $this->mFlags &= ~DBO_TRX; 00674 if ( $wgDebugDBTransactions ) { 00675 wfDebug( "Implicit transaction open disabled.\n" ); 00676 } 00677 } else { 00678 $this->mFlags |= DBO_TRX; 00679 if ( $wgDebugDBTransactions ) { 00680 wfDebug( "Implicit transaction open enabled.\n" ); 00681 } 00682 } 00683 } 00684 00686 if ( $tablePrefix == 'get from global' ) { 00687 $this->mTablePrefix = $wgDBprefix; 00688 } else { 00689 $this->mTablePrefix = $tablePrefix; 00690 } 00691 00692 if ( $user ) { 00693 $this->open( $server, $user, $password, $dbName ); 00694 } 00695 } 00696 00702 public function __sleep() { 00703 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' ); 00704 } 00705 00728 final public static function factory( $dbType, $p = array() ) { 00729 $canonicalDBTypes = array( 00730 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql' 00731 ); 00732 $dbType = strtolower( $dbType ); 00733 $class = 'Database' . ucfirst( $dbType ); 00734 00735 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) { 00736 return new $class( 00737 isset( $p['host'] ) ? $p['host'] : false, 00738 isset( $p['user'] ) ? $p['user'] : false, 00739 isset( $p['password'] ) ? $p['password'] : false, 00740 isset( $p['dbname'] ) ? $p['dbname'] : false, 00741 isset( $p['flags'] ) ? $p['flags'] : 0, 00742 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global' 00743 ); 00744 } else { 00745 return null; 00746 } 00747 } 00748 00749 protected function installErrorHandler() { 00750 $this->mPHPError = false; 00751 $this->htmlErrors = ini_set( 'html_errors', '0' ); 00752 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 00753 } 00754 00758 protected function restoreErrorHandler() { 00759 restore_error_handler(); 00760 if ( $this->htmlErrors !== false ) { 00761 ini_set( 'html_errors', $this->htmlErrors ); 00762 } 00763 if ( $this->mPHPError ) { 00764 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 00765 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 00766 return $error; 00767 } else { 00768 return false; 00769 } 00770 } 00771 00776 protected function connectionErrorHandler( $errno, $errstr ) { 00777 $this->mPHPError = $errstr; 00778 } 00779 00787 public function close() { 00788 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 00789 throw new MWException( "Transaction idle callbacks still pending." ); 00790 } 00791 $this->mOpened = false; 00792 if ( $this->mConn ) { 00793 if ( $this->trxLevel() ) { 00794 if ( !$this->mTrxAutomatic ) { 00795 wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . 00796 " performing implicit commit before closing connection!" ); 00797 } 00798 00799 $this->commit( __METHOD__, 'flush' ); 00800 } 00801 00802 $ret = $this->closeConnection(); 00803 $this->mConn = false; 00804 return $ret; 00805 } else { 00806 return true; 00807 } 00808 } 00809 00815 abstract protected function closeConnection(); 00816 00821 function reportConnectionError( $error = 'Unknown error' ) { 00822 $myError = $this->lastError(); 00823 if ( $myError ) { 00824 $error = $myError; 00825 } 00826 00827 # New method 00828 throw new DBConnectionError( $this, $error ); 00829 } 00830 00837 abstract protected function doQuery( $sql ); 00838 00847 public function isWriteQuery( $sql ) { 00848 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 00849 } 00850 00873 public function query( $sql, $fname = '', $tempIgnore = false ) { 00874 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 00875 if ( !Profiler::instance()->isStub() ) { 00876 # generalizeSQL will probably cut down the query to reasonable 00877 # logging size most of the time. The substr is really just a sanity check. 00878 00879 if ( $isMaster ) { 00880 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00881 $totalProf = 'DatabaseBase::query-master'; 00882 } else { 00883 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00884 $totalProf = 'DatabaseBase::query'; 00885 } 00886 00887 wfProfileIn( $totalProf ); 00888 wfProfileIn( $queryProf ); 00889 } 00890 00891 $this->mLastQuery = $sql; 00892 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { 00893 # Set a flag indicating that writes have been done 00894 wfDebug( __METHOD__ . ": Writes done: $sql\n" ); 00895 $this->mDoneWrites = true; 00896 } 00897 00898 # Add a comment for easy SHOW PROCESSLIST interpretation 00899 global $wgUser; 00900 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 00901 $userName = $wgUser->getName(); 00902 if ( mb_strlen( $userName ) > 15 ) { 00903 $userName = mb_substr( $userName, 0, 15 ) . '...'; 00904 } 00905 $userName = str_replace( '/', '', $userName ); 00906 } else { 00907 $userName = ''; 00908 } 00909 00910 // Add trace comment to the begin of the sql string, right after the operator. 00911 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) 00912 $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 ); 00913 00914 # If DBO_TRX is set, start a transaction 00915 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && 00916 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) 00917 { 00918 # Avoid establishing transactions for SHOW and SET statements too - 00919 # that would delay transaction initializations to once connection 00920 # is really used by application 00921 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 00922 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 00923 global $wgDebugDBTransactions; 00924 if ( $wgDebugDBTransactions ) { 00925 wfDebug( "Implicit transaction start.\n" ); 00926 } 00927 $this->begin( __METHOD__ . " ($fname)" ); 00928 $this->mTrxAutomatic = true; 00929 } 00930 } 00931 00932 # Keep track of whether the transaction has write queries pending 00933 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { 00934 $this->mTrxDoneWrites = true; 00935 } 00936 00937 if ( $this->debug() ) { 00938 static $cnt = 0; 00939 00940 $cnt++; 00941 $sqlx = substr( $commentedSql, 0, 500 ); 00942 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 00943 00944 $master = $isMaster ? 'master' : 'slave'; 00945 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 00946 } 00947 00948 if ( istainted( $sql ) & TC_MYSQL ) { 00949 throw new MWException( 'Tainted query found' ); 00950 } 00951 00952 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 00953 00954 # Do the query and handle errors 00955 $ret = $this->doQuery( $commentedSql ); 00956 00957 MWDebug::queryTime( $queryId ); 00958 00959 # Try reconnecting if the connection was lost 00960 if ( false === $ret && $this->wasErrorReissuable() ) { 00961 # Transaction is gone, like it or not 00962 $this->mTrxLevel = 0; 00963 $this->mTrxIdleCallbacks = array(); // cancel 00964 wfDebug( "Connection lost, reconnecting...\n" ); 00965 00966 if ( $this->ping() ) { 00967 wfDebug( "Reconnected\n" ); 00968 $sqlx = substr( $commentedSql, 0, 500 ); 00969 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 00970 global $wgRequestTime; 00971 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 00972 if ( $elapsed < 300 ) { 00973 # Not a database error to lose a transaction after a minute or two 00974 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); 00975 } 00976 $ret = $this->doQuery( $commentedSql ); 00977 } else { 00978 wfDebug( "Failed\n" ); 00979 } 00980 } 00981 00982 if ( false === $ret ) { 00983 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 00984 } 00985 00986 if ( !Profiler::instance()->isStub() ) { 00987 wfProfileOut( $queryProf ); 00988 wfProfileOut( $totalProf ); 00989 } 00990 00991 return $this->resultObject( $ret ); 00992 } 00993 01005 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 01006 # Ignore errors during error handling to avoid infinite recursion 01007 $ignore = $this->ignoreErrors( true ); 01008 ++$this->mErrorCount; 01009 01010 if ( $ignore || $tempIgnore ) { 01011 wfDebug( "SQL ERROR (ignored): $error\n" ); 01012 $this->ignoreErrors( $ignore ); 01013 } else { 01014 $sql1line = str_replace( "\n", "\\n", $sql ); 01015 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" ); 01016 wfDebug( "SQL ERROR: " . $error . "\n" ); 01017 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 01018 } 01019 } 01020 01035 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 01036 /* MySQL doesn't support prepared statements (yet), so just 01037 pack up the query for reference. We'll manually replace 01038 the bits later. */ 01039 return array( 'query' => $sql, 'func' => $func ); 01040 } 01041 01046 protected function freePrepared( $prepared ) { 01047 /* No-op by default */ 01048 } 01049 01057 public function execute( $prepared, $args = null ) { 01058 if ( !is_array( $args ) ) { 01059 # Pull the var args 01060 $args = func_get_args(); 01061 array_shift( $args ); 01062 } 01063 01064 $sql = $this->fillPrepared( $prepared['query'], $args ); 01065 01066 return $this->query( $sql, $prepared['func'] ); 01067 } 01068 01076 public function fillPrepared( $preparedQuery, $args ) { 01077 reset( $args ); 01078 $this->preparedArgs =& $args; 01079 01080 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 01081 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 01082 } 01083 01093 protected function fillPreparedArg( $matches ) { 01094 switch( $matches[1] ) { 01095 case '\\?': return '?'; 01096 case '\\!': return '!'; 01097 case '\\&': return '&'; 01098 } 01099 01100 list( /* $n */, $arg ) = each( $this->preparedArgs ); 01101 01102 switch( $matches[1] ) { 01103 case '?': return $this->addQuotes( $arg ); 01104 case '!': return $arg; 01105 case '&': 01106 # return $this->addQuotes( file_get_contents( $arg ) ); 01107 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); 01108 default: 01109 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); 01110 } 01111 } 01112 01120 public function freeResult( $res ) {} 01121 01139 public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField', 01140 $options = array() ) 01141 { 01142 if ( !is_array( $options ) ) { 01143 $options = array( $options ); 01144 } 01145 01146 $options['LIMIT'] = 1; 01147 01148 $res = $this->select( $table, $var, $cond, $fname, $options ); 01149 01150 if ( $res === false || !$this->numRows( $res ) ) { 01151 return false; 01152 } 01153 01154 $row = $this->fetchRow( $res ); 01155 01156 if ( $row !== false ) { 01157 return reset( $row ); 01158 } else { 01159 return false; 01160 } 01161 } 01162 01172 public function makeSelectOptions( $options ) { 01173 $preLimitTail = $postLimitTail = ''; 01174 $startOpts = ''; 01175 01176 $noKeyOptions = array(); 01177 01178 foreach ( $options as $key => $option ) { 01179 if ( is_numeric( $key ) ) { 01180 $noKeyOptions[$option] = true; 01181 } 01182 } 01183 01184 $preLimitTail .= $this->makeGroupByWithHaving( $options ); 01185 01186 $preLimitTail .= $this->makeOrderBy( $options ); 01187 01188 // if (isset($options['LIMIT'])) { 01189 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 01190 // isset($options['OFFSET']) ? $options['OFFSET'] 01191 // : false); 01192 // } 01193 01194 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 01195 $postLimitTail .= ' FOR UPDATE'; 01196 } 01197 01198 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 01199 $postLimitTail .= ' LOCK IN SHARE MODE'; 01200 } 01201 01202 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01203 $startOpts .= 'DISTINCT'; 01204 } 01205 01206 # Various MySQL extensions 01207 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 01208 $startOpts .= ' /*! STRAIGHT_JOIN */'; 01209 } 01210 01211 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 01212 $startOpts .= ' HIGH_PRIORITY'; 01213 } 01214 01215 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 01216 $startOpts .= ' SQL_BIG_RESULT'; 01217 } 01218 01219 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 01220 $startOpts .= ' SQL_BUFFER_RESULT'; 01221 } 01222 01223 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 01224 $startOpts .= ' SQL_SMALL_RESULT'; 01225 } 01226 01227 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 01228 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 01229 } 01230 01231 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 01232 $startOpts .= ' SQL_CACHE'; 01233 } 01234 01235 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 01236 $startOpts .= ' SQL_NO_CACHE'; 01237 } 01238 01239 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { 01240 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01241 } else { 01242 $useIndex = ''; 01243 } 01244 01245 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01246 } 01247 01256 public function makeGroupByWithHaving( $options ) { 01257 $sql = ''; 01258 if ( isset( $options['GROUP BY'] ) ) { 01259 $gb = is_array( $options['GROUP BY'] ) 01260 ? implode( ',', $options['GROUP BY'] ) 01261 : $options['GROUP BY']; 01262 $sql .= ' GROUP BY ' . $gb; 01263 } 01264 if ( isset( $options['HAVING'] ) ) { 01265 $having = is_array( $options['HAVING'] ) 01266 ? $this->makeList( $options['HAVING'], LIST_AND ) 01267 : $options['HAVING']; 01268 $sql .= ' HAVING ' . $having; 01269 } 01270 return $sql; 01271 } 01272 01281 public function makeOrderBy( $options ) { 01282 if ( isset( $options['ORDER BY'] ) ) { 01283 $ob = is_array( $options['ORDER BY'] ) 01284 ? implode( ',', $options['ORDER BY'] ) 01285 : $options['ORDER BY']; 01286 return ' ORDER BY ' . $ob; 01287 } 01288 return ''; 01289 } 01290 01430 public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', 01431 $options = array(), $join_conds = array() ) { 01432 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 01433 01434 return $this->query( $sql, $fname ); 01435 } 01436 01453 public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', 01454 $options = array(), $join_conds = array() ) 01455 { 01456 if ( is_array( $vars ) ) { 01457 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 01458 } 01459 01460 $options = (array)$options; 01461 01462 if ( is_array( $table ) ) { 01463 $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 01464 ? $options['USE INDEX'] 01465 : array(); 01466 if ( count( $join_conds ) || count( $useIndex ) ) { 01467 $from = ' FROM ' . 01468 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds ); 01469 } else { 01470 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) ); 01471 } 01472 } elseif ( $table != '' ) { 01473 if ( $table[0] == ' ' ) { 01474 $from = ' FROM ' . $table; 01475 } else { 01476 $from = ' FROM ' . $this->tableName( $table ); 01477 } 01478 } else { 01479 $from = ''; 01480 } 01481 01482 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options ); 01483 01484 if ( !empty( $conds ) ) { 01485 if ( is_array( $conds ) ) { 01486 $conds = $this->makeList( $conds, LIST_AND ); 01487 } 01488 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 01489 } else { 01490 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 01491 } 01492 01493 if ( isset( $options['LIMIT'] ) ) { 01494 $sql = $this->limitResult( $sql, $options['LIMIT'], 01495 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 01496 } 01497 $sql = "$sql $postLimitTail"; 01498 01499 if ( isset( $options['EXPLAIN'] ) ) { 01500 $sql = 'EXPLAIN ' . $sql; 01501 } 01502 01503 return $sql; 01504 } 01505 01520 public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow', 01521 $options = array(), $join_conds = array() ) 01522 { 01523 $options = (array)$options; 01524 $options['LIMIT'] = 1; 01525 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 01526 01527 if ( $res === false ) { 01528 return false; 01529 } 01530 01531 if ( !$this->numRows( $res ) ) { 01532 return false; 01533 } 01534 01535 $obj = $this->fetchObject( $res ); 01536 01537 return $obj; 01538 } 01539 01560 public function estimateRowCount( $table, $vars = '*', $conds = '', 01561 $fname = 'DatabaseBase::estimateRowCount', $options = array() ) 01562 { 01563 $rows = 0; 01564 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 01565 01566 if ( $res ) { 01567 $row = $this->fetchRow( $res ); 01568 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01569 } 01570 01571 return $rows; 01572 } 01573 01582 static function generalizeSQL( $sql ) { 01583 # This does the same as the regexp below would do, but in such a way 01584 # as to avoid crashing php on some large strings. 01585 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); 01586 01587 $sql = str_replace ( "\\\\", '', $sql ); 01588 $sql = str_replace ( "\\'", '', $sql ); 01589 $sql = str_replace ( "\\\"", '', $sql ); 01590 $sql = preg_replace ( "/'.*'/s", "'X'", $sql ); 01591 $sql = preg_replace ( '/".*"/s', "'X'", $sql ); 01592 01593 # All newlines, tabs, etc replaced by single space 01594 $sql = preg_replace ( '/\s+/', ' ', $sql ); 01595 01596 # All numbers => N 01597 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql ); 01598 01599 return $sql; 01600 } 01601 01610 public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) { 01611 $info = $this->fieldInfo( $table, $field ); 01612 01613 return (bool)$info; 01614 } 01615 01627 public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) { 01628 if( !$this->tableExists( $table ) ) { 01629 return null; 01630 } 01631 01632 $info = $this->indexInfo( $table, $index, $fname ); 01633 if ( is_null( $info ) ) { 01634 return null; 01635 } else { 01636 return $info !== false; 01637 } 01638 } 01639 01648 public function tableExists( $table, $fname = __METHOD__ ) { 01649 $table = $this->tableName( $table ); 01650 $old = $this->ignoreErrors( true ); 01651 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 01652 $this->ignoreErrors( $old ); 01653 01654 return (bool)$res; 01655 } 01656 01663 public function fieldType( $res, $index ) { 01664 if ( $res instanceof ResultWrapper ) { 01665 $res = $res->result; 01666 } 01667 01668 return mysql_field_type( $res, $index ); 01669 } 01670 01679 public function indexUnique( $table, $index ) { 01680 $indexInfo = $this->indexInfo( $table, $index ); 01681 01682 if ( !$indexInfo ) { 01683 return null; 01684 } 01685 01686 return !$indexInfo[0]->Non_unique; 01687 } 01688 01695 protected function makeInsertOptions( $options ) { 01696 return implode( ' ', $options ); 01697 } 01698 01732 public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) { 01733 # No rows to insert, easy just return now 01734 if ( !count( $a ) ) { 01735 return true; 01736 } 01737 01738 $table = $this->tableName( $table ); 01739 01740 if ( !is_array( $options ) ) { 01741 $options = array( $options ); 01742 } 01743 01744 $fh = null; 01745 if ( isset( $options['fileHandle'] ) ) { 01746 $fh = $options['fileHandle']; 01747 } 01748 $options = $this->makeInsertOptions( $options ); 01749 01750 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 01751 $multi = true; 01752 $keys = array_keys( $a[0] ); 01753 } else { 01754 $multi = false; 01755 $keys = array_keys( $a ); 01756 } 01757 01758 $sql = 'INSERT ' . $options . 01759 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 01760 01761 if ( $multi ) { 01762 $first = true; 01763 foreach ( $a as $row ) { 01764 if ( $first ) { 01765 $first = false; 01766 } else { 01767 $sql .= ','; 01768 } 01769 $sql .= '(' . $this->makeList( $row ) . ')'; 01770 } 01771 } else { 01772 $sql .= '(' . $this->makeList( $a ) . ')'; 01773 } 01774 01775 if ( $fh !== null && false === fwrite( $fh, $sql ) ) { 01776 return false; 01777 } elseif ( $fh !== null ) { 01778 return true; 01779 } 01780 01781 return (bool)$this->query( $sql, $fname ); 01782 } 01783 01790 protected function makeUpdateOptions( $options ) { 01791 if ( !is_array( $options ) ) { 01792 $options = array( $options ); 01793 } 01794 01795 $opts = array(); 01796 01797 if ( in_array( 'LOW_PRIORITY', $options ) ) { 01798 $opts[] = $this->lowPriorityOption(); 01799 } 01800 01801 if ( in_array( 'IGNORE', $options ) ) { 01802 $opts[] = 'IGNORE'; 01803 } 01804 01805 return implode( ' ', $opts ); 01806 } 01807 01831 function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) { 01832 $table = $this->tableName( $table ); 01833 $opts = $this->makeUpdateOptions( $options ); 01834 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 01835 01836 if ( $conds !== array() && $conds !== '*' ) { 01837 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 01838 } 01839 01840 return $this->query( $sql, $fname ); 01841 } 01842 01857 public function makeList( $a, $mode = LIST_COMMA ) { 01858 if ( !is_array( $a ) ) { 01859 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 01860 } 01861 01862 $first = true; 01863 $list = ''; 01864 01865 foreach ( $a as $field => $value ) { 01866 if ( !$first ) { 01867 if ( $mode == LIST_AND ) { 01868 $list .= ' AND '; 01869 } elseif ( $mode == LIST_OR ) { 01870 $list .= ' OR '; 01871 } else { 01872 $list .= ','; 01873 } 01874 } else { 01875 $first = false; 01876 } 01877 01878 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 01879 $list .= "($value)"; 01880 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 01881 $list .= "$value"; 01882 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 01883 if ( count( $value ) == 0 ) { 01884 throw new MWException( __METHOD__ . ": empty input for field $field" ); 01885 } elseif ( count( $value ) == 1 ) { 01886 // Special-case single values, as IN isn't terribly efficient 01887 // Don't necessarily assume the single key is 0; we don't 01888 // enforce linear numeric ordering on other arrays here. 01889 $value = array_values( $value ); 01890 $list .= $field . " = " . $this->addQuotes( $value[0] ); 01891 } else { 01892 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 01893 } 01894 } elseif ( $value === null ) { 01895 if ( $mode == LIST_AND || $mode == LIST_OR ) { 01896 $list .= "$field IS "; 01897 } elseif ( $mode == LIST_SET ) { 01898 $list .= "$field = "; 01899 } 01900 $list .= 'NULL'; 01901 } else { 01902 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 01903 $list .= "$field = "; 01904 } 01905 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 01906 } 01907 } 01908 01909 return $list; 01910 } 01911 01922 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 01923 $conds = array(); 01924 01925 foreach ( $data as $base => $sub ) { 01926 if ( count( $sub ) ) { 01927 $conds[] = $this->makeList( 01928 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 01929 LIST_AND ); 01930 } 01931 } 01932 01933 if ( $conds ) { 01934 return $this->makeList( $conds, LIST_OR ); 01935 } else { 01936 // Nothing to search for... 01937 return false; 01938 } 01939 } 01940 01949 public function aggregateValue( $valuedata, $valuename = 'value' ) { 01950 return $valuename; 01951 } 01952 01957 public function bitNot( $field ) { 01958 return "(~$field)"; 01959 } 01960 01966 public function bitAnd( $fieldLeft, $fieldRight ) { 01967 return "($fieldLeft & $fieldRight)"; 01968 } 01969 01975 public function bitOr( $fieldLeft, $fieldRight ) { 01976 return "($fieldLeft | $fieldRight)"; 01977 } 01978 01984 public function buildConcat( $stringList ) { 01985 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 01986 } 01987 01997 public function selectDB( $db ) { 01998 # Stub. Shouldn't cause serious problems if it's not overridden, but 01999 # if your database engine supports a concept similar to MySQL's 02000 # databases you may as well. 02001 $this->mDBname = $db; 02002 return true; 02003 } 02004 02008 public function getDBname() { 02009 return $this->mDBname; 02010 } 02011 02015 public function getServer() { 02016 return $this->mServer; 02017 } 02018 02036 public function tableName( $name, $format = 'quoted' ) { 02037 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables; 02038 # Skip the entire process when we have a string quoted on both ends. 02039 # Note that we check the end so that we will still quote any use of 02040 # use of `database`.table. But won't break things if someone wants 02041 # to query a database table with a dot in the name. 02042 if ( $this->isQuotedIdentifier( $name ) ) { 02043 return $name; 02044 } 02045 02046 # Lets test for any bits of text that should never show up in a table 02047 # name. Basically anything like JOIN or ON which are actually part of 02048 # SQL queries, but may end up inside of the table value to combine 02049 # sql. Such as how the API is doing. 02050 # Note that we use a whitespace test rather than a \b test to avoid 02051 # any remote case where a word like on may be inside of a table name 02052 # surrounded by symbols which may be considered word breaks. 02053 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 02054 return $name; 02055 } 02056 02057 # Split database and table into proper variables. 02058 # We reverse the explode so that database.table and table both output 02059 # the correct table. 02060 $dbDetails = explode( '.', $name, 2 ); 02061 if ( count( $dbDetails ) == 2 ) { 02062 list( $database, $table ) = $dbDetails; 02063 # We don't want any prefix added in this case 02064 $prefix = ''; 02065 } else { 02066 list( $table ) = $dbDetails; 02067 if ( $wgSharedDB !== null # We have a shared database 02068 && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' 02069 && in_array( $table, $wgSharedTables ) # A shared table is selected 02070 ) { 02071 $database = $wgSharedDB; 02072 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; 02073 } else { 02074 $database = null; 02075 $prefix = $this->mTablePrefix; # Default prefix 02076 } 02077 } 02078 02079 # Quote $table and apply the prefix if not quoted. 02080 $tableName = "{$prefix}{$table}"; 02081 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) { 02082 $tableName = $this->addIdentifierQuotes( $tableName ); 02083 } 02084 02085 # Quote $database and merge it with the table name if needed 02086 if ( $database !== null ) { 02087 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 02088 $database = $this->addIdentifierQuotes( $database ); 02089 } 02090 $tableName = $database . '.' . $tableName; 02091 } 02092 02093 return $tableName; 02094 } 02095 02107 public function tableNames() { 02108 $inArray = func_get_args(); 02109 $retVal = array(); 02110 02111 foreach ( $inArray as $name ) { 02112 $retVal[$name] = $this->tableName( $name ); 02113 } 02114 02115 return $retVal; 02116 } 02117 02129 public function tableNamesN() { 02130 $inArray = func_get_args(); 02131 $retVal = array(); 02132 02133 foreach ( $inArray as $name ) { 02134 $retVal[] = $this->tableName( $name ); 02135 } 02136 02137 return $retVal; 02138 } 02139 02148 public function tableNameWithAlias( $name, $alias = false ) { 02149 if ( !$alias || $alias == $name ) { 02150 return $this->tableName( $name ); 02151 } else { 02152 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 02153 } 02154 } 02155 02162 public function tableNamesWithAlias( $tables ) { 02163 $retval = array(); 02164 foreach ( $tables as $alias => $table ) { 02165 if ( is_numeric( $alias ) ) { 02166 $alias = $table; 02167 } 02168 $retval[] = $this->tableNameWithAlias( $table, $alias ); 02169 } 02170 return $retval; 02171 } 02172 02181 public function fieldNameWithAlias( $name, $alias = false ) { 02182 if ( !$alias || (string)$alias === (string)$name ) { 02183 return $name; 02184 } else { 02185 return $name . ' AS ' . $alias; //PostgreSQL needs AS 02186 } 02187 } 02188 02195 public function fieldNamesWithAlias( $fields ) { 02196 $retval = array(); 02197 foreach ( $fields as $alias => $field ) { 02198 if ( is_numeric( $alias ) ) { 02199 $alias = $field; 02200 } 02201 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 02202 } 02203 return $retval; 02204 } 02205 02215 protected function tableNamesWithUseIndexOrJOIN( 02216 $tables, $use_index = array(), $join_conds = array() 02217 ) { 02218 $ret = array(); 02219 $retJOIN = array(); 02220 $use_index = (array)$use_index; 02221 $join_conds = (array)$join_conds; 02222 02223 foreach ( $tables as $alias => $table ) { 02224 if ( !is_string( $alias ) ) { 02225 // No alias? Set it equal to the table name 02226 $alias = $table; 02227 } 02228 // Is there a JOIN clause for this table? 02229 if ( isset( $join_conds[$alias] ) ) { 02230 list( $joinType, $conds ) = $join_conds[$alias]; 02231 $tableClause = $joinType; 02232 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 02233 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 02234 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 02235 if ( $use != '' ) { 02236 $tableClause .= ' ' . $use; 02237 } 02238 } 02239 $on = $this->makeList( (array)$conds, LIST_AND ); 02240 if ( $on != '' ) { 02241 $tableClause .= ' ON (' . $on . ')'; 02242 } 02243 02244 $retJOIN[] = $tableClause; 02245 // Is there an INDEX clause for this table? 02246 } elseif ( isset( $use_index[$alias] ) ) { 02247 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02248 $tableClause .= ' ' . $this->useIndexClause( 02249 implode( ',', (array)$use_index[$alias] ) ); 02250 02251 $ret[] = $tableClause; 02252 } else { 02253 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02254 02255 $ret[] = $tableClause; 02256 } 02257 } 02258 02259 // We can't separate explicit JOIN clauses with ',', use ' ' for those 02260 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 02261 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 02262 02263 // Compile our final table clause 02264 return implode( ' ', array( $straightJoins, $otherJoins ) ); 02265 } 02266 02274 protected function indexName( $index ) { 02275 // Backwards-compatibility hack 02276 $renamed = array( 02277 'ar_usertext_timestamp' => 'usertext_timestamp', 02278 'un_user_id' => 'user_id', 02279 'un_user_ip' => 'user_ip', 02280 ); 02281 02282 if ( isset( $renamed[$index] ) ) { 02283 return $renamed[$index]; 02284 } else { 02285 return $index; 02286 } 02287 } 02288 02297 public function addQuotes( $s ) { 02298 if ( $s === null ) { 02299 return 'NULL'; 02300 } else { 02301 # This will also quote numeric values. This should be harmless, 02302 # and protects against weird problems that occur when they really 02303 # _are_ strings such as article titles and string->number->string 02304 # conversion is not 1:1. 02305 return "'" . $this->strencode( $s ) . "'"; 02306 } 02307 } 02308 02319 public function addIdentifierQuotes( $s ) { 02320 return '"' . str_replace( '"', '""', $s ) . '"'; 02321 } 02322 02331 public function isQuotedIdentifier( $name ) { 02332 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 02333 } 02334 02339 protected function escapeLikeInternal( $s ) { 02340 $s = str_replace( '\\', '\\\\', $s ); 02341 $s = $this->strencode( $s ); 02342 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s ); 02343 02344 return $s; 02345 } 02346 02359 public function buildLike() { 02360 $params = func_get_args(); 02361 02362 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 02363 $params = $params[0]; 02364 } 02365 02366 $s = ''; 02367 02368 foreach ( $params as $value ) { 02369 if ( $value instanceof LikeMatch ) { 02370 $s .= $value->toString(); 02371 } else { 02372 $s .= $this->escapeLikeInternal( $value ); 02373 } 02374 } 02375 02376 return " LIKE '" . $s . "' "; 02377 } 02378 02384 public function anyChar() { 02385 return new LikeMatch( '_' ); 02386 } 02387 02393 public function anyString() { 02394 return new LikeMatch( '%' ); 02395 } 02396 02408 public function nextSequenceValue( $seqName ) { 02409 return null; 02410 } 02411 02422 public function useIndexClause( $index ) { 02423 return ''; 02424 } 02425 02448 public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) { 02449 $quotedTable = $this->tableName( $table ); 02450 02451 if ( count( $rows ) == 0 ) { 02452 return; 02453 } 02454 02455 # Single row case 02456 if ( !is_array( reset( $rows ) ) ) { 02457 $rows = array( $rows ); 02458 } 02459 02460 foreach( $rows as $row ) { 02461 # Delete rows which collide 02462 if ( $uniqueIndexes ) { 02463 $sql = "DELETE FROM $quotedTable WHERE "; 02464 $first = true; 02465 foreach ( $uniqueIndexes as $index ) { 02466 if ( $first ) { 02467 $first = false; 02468 $sql .= '( '; 02469 } else { 02470 $sql .= ' ) OR ( '; 02471 } 02472 if ( is_array( $index ) ) { 02473 $first2 = true; 02474 foreach ( $index as $col ) { 02475 if ( $first2 ) { 02476 $first2 = false; 02477 } else { 02478 $sql .= ' AND '; 02479 } 02480 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 02481 } 02482 } else { 02483 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 02484 } 02485 } 02486 $sql .= ' )'; 02487 $this->query( $sql, $fname ); 02488 } 02489 02490 # Now insert the row 02491 $this->insert( $table, $row ); 02492 } 02493 } 02494 02505 protected function nativeReplace( $table, $rows, $fname ) { 02506 $table = $this->tableName( $table ); 02507 02508 # Single row case 02509 if ( !is_array( reset( $rows ) ) ) { 02510 $rows = array( $rows ); 02511 } 02512 02513 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 02514 $first = true; 02515 02516 foreach ( $rows as $row ) { 02517 if ( $first ) { 02518 $first = false; 02519 } else { 02520 $sql .= ','; 02521 } 02522 02523 $sql .= '(' . $this->makeList( $row ) . ')'; 02524 } 02525 02526 return $this->query( $sql, $fname ); 02527 } 02528 02550 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 02551 $fname = 'DatabaseBase::deleteJoin' ) 02552 { 02553 if ( !$conds ) { 02554 throw new DBUnexpectedError( $this, 02555 'DatabaseBase::deleteJoin() called with empty $conds' ); 02556 } 02557 02558 $delTable = $this->tableName( $delTable ); 02559 $joinTable = $this->tableName( $joinTable ); 02560 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 02561 if ( $conds != '*' ) { 02562 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 02563 } 02564 $sql .= ')'; 02565 02566 $this->query( $sql, $fname ); 02567 } 02568 02577 public function textFieldSize( $table, $field ) { 02578 $table = $this->tableName( $table ); 02579 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 02580 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 02581 $row = $this->fetchObject( $res ); 02582 02583 $m = array(); 02584 02585 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 02586 $size = $m[1]; 02587 } else { 02588 $size = -1; 02589 } 02590 02591 return $size; 02592 } 02593 02602 public function lowPriorityOption() { 02603 return ''; 02604 } 02605 02617 public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) { 02618 if ( !$conds ) { 02619 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 02620 } 02621 02622 $table = $this->tableName( $table ); 02623 $sql = "DELETE FROM $table"; 02624 02625 if ( $conds != '*' ) { 02626 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 02627 } 02628 02629 return $this->query( $sql, $fname ); 02630 } 02631 02658 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 02659 $fname = 'DatabaseBase::insertSelect', 02660 $insertOptions = array(), $selectOptions = array() ) 02661 { 02662 $destTable = $this->tableName( $destTable ); 02663 02664 if ( is_array( $insertOptions ) ) { 02665 $insertOptions = implode( ' ', $insertOptions ); 02666 } 02667 02668 if ( !is_array( $selectOptions ) ) { 02669 $selectOptions = array( $selectOptions ); 02670 } 02671 02672 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 02673 02674 if ( is_array( $srcTable ) ) { 02675 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 02676 } else { 02677 $srcTable = $this->tableName( $srcTable ); 02678 } 02679 02680 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 02681 " SELECT $startOpts " . implode( ',', $varMap ) . 02682 " FROM $srcTable $useIndex "; 02683 02684 if ( $conds != '*' ) { 02685 if ( is_array( $conds ) ) { 02686 $conds = $this->makeList( $conds, LIST_AND ); 02687 } 02688 $sql .= " WHERE $conds"; 02689 } 02690 02691 $sql .= " $tailOpts"; 02692 02693 return $this->query( $sql, $fname ); 02694 } 02695 02716 public function limitResult( $sql, $limit, $offset = false ) { 02717 if ( !is_numeric( $limit ) ) { 02718 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 02719 } 02720 return "$sql LIMIT " 02721 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 02722 . "{$limit} "; 02723 } 02724 02730 public function unionSupportsOrderAndLimit() { 02731 return true; // True for almost every DB supported 02732 } 02733 02742 public function unionQueries( $sqls, $all ) { 02743 $glue = $all ? ') UNION ALL (' : ') UNION ('; 02744 return '(' . implode( $glue, $sqls ) . ')'; 02745 } 02746 02756 public function conditional( $cond, $trueVal, $falseVal ) { 02757 if ( is_array( $cond ) ) { 02758 $cond = $this->makeList( $cond, LIST_AND ); 02759 } 02760 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 02761 } 02762 02773 public function strreplace( $orig, $old, $new ) { 02774 return "REPLACE({$orig}, {$old}, {$new})"; 02775 } 02776 02783 public function getServerUptime() { 02784 return 0; 02785 } 02786 02793 public function wasDeadlock() { 02794 return false; 02795 } 02796 02803 public function wasLockTimeout() { 02804 return false; 02805 } 02806 02814 public function wasErrorReissuable() { 02815 return false; 02816 } 02817 02824 public function wasReadOnlyError() { 02825 return false; 02826 } 02827 02846 public function deadlockLoop() { 02847 $this->begin( __METHOD__ ); 02848 $args = func_get_args(); 02849 $function = array_shift( $args ); 02850 $oldIgnore = $this->ignoreErrors( true ); 02851 $tries = DEADLOCK_TRIES; 02852 02853 if ( is_array( $function ) ) { 02854 $fname = $function[0]; 02855 } else { 02856 $fname = $function; 02857 } 02858 02859 do { 02860 $retVal = call_user_func_array( $function, $args ); 02861 $error = $this->lastError(); 02862 $errno = $this->lastErrno(); 02863 $sql = $this->lastQuery(); 02864 02865 if ( $errno ) { 02866 if ( $this->wasDeadlock() ) { 02867 # Retry 02868 usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) ); 02869 } else { 02870 $this->reportQueryError( $error, $errno, $sql, $fname ); 02871 } 02872 } 02873 } while ( $this->wasDeadlock() && --$tries > 0 ); 02874 02875 $this->ignoreErrors( $oldIgnore ); 02876 02877 if ( $tries <= 0 ) { 02878 $this->rollback( __METHOD__ ); 02879 $this->reportQueryError( $error, $errno, $sql, $fname ); 02880 return false; 02881 } else { 02882 $this->commit( __METHOD__ ); 02883 return $retVal; 02884 } 02885 } 02886 02898 public function masterPosWait( DBMasterPos $pos, $timeout ) { 02899 wfProfileIn( __METHOD__ ); 02900 02901 if ( !is_null( $this->mFakeSlaveLag ) ) { 02902 $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 ); 02903 02904 if ( $wait > $timeout * 1e6 ) { 02905 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" ); 02906 wfProfileOut( __METHOD__ ); 02907 return -1; 02908 } elseif ( $wait > 0 ) { 02909 wfDebug( "Fake slave waiting $wait us\n" ); 02910 usleep( $wait ); 02911 wfProfileOut( __METHOD__ ); 02912 return 1; 02913 } else { 02914 wfDebug( "Fake slave up to date ($wait us)\n" ); 02915 wfProfileOut( __METHOD__ ); 02916 return 0; 02917 } 02918 } 02919 02920 wfProfileOut( __METHOD__ ); 02921 02922 # Real waits are implemented in the subclass. 02923 return 0; 02924 } 02925 02931 public function getSlavePos() { 02932 if ( !is_null( $this->mFakeSlaveLag ) ) { 02933 $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag ); 02934 wfDebug( __METHOD__ . ": fake slave pos = $pos\n" ); 02935 return $pos; 02936 } else { 02937 # Stub 02938 return false; 02939 } 02940 } 02941 02947 public function getMasterPos() { 02948 if ( $this->mFakeMaster ) { 02949 return new MySQLMasterPos( 'fake', microtime( true ) ); 02950 } else { 02951 return false; 02952 } 02953 } 02954 02966 final public function onTransactionIdle( Closure $callback ) { 02967 if ( $this->mTrxLevel ) { 02968 $this->mTrxIdleCallbacks[] = $callback; 02969 } else { 02970 $callback(); 02971 } 02972 } 02973 02979 protected function runOnTransactionIdleCallbacks() { 02980 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? 02981 02982 $e = null; // last exception 02983 do { // callbacks may add callbacks :) 02984 $callbacks = $this->mTrxIdleCallbacks; 02985 $this->mTrxIdleCallbacks = array(); // recursion guard 02986 foreach ( $callbacks as $callback ) { 02987 try { 02988 $this->clearFlag( DBO_TRX ); // make each query its own transaction 02989 $callback(); 02990 $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() 02991 } catch ( Exception $e ) {} 02992 } 02993 } while ( count( $this->mTrxIdleCallbacks ) ); 02994 02995 if ( $e instanceof Exception ) { 02996 throw $e; // re-throw any last exception 02997 } 02998 } 02999 03012 final public function begin( $fname = 'DatabaseBase::begin' ) { 03013 global $wgDebugDBTransactions; 03014 03015 if ( $this->mTrxLevel ) { // implicit commit 03016 if ( !$this->mTrxAutomatic ) { 03017 // We want to warn about inadvertently nested begin/commit pairs, but not about 03018 // auto-committing implicit transactions that were started by query() via DBO_TRX 03019 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " . 03020 " performing implicit commit!"; 03021 wfWarn( $msg ); 03022 wfLogDBError( $msg ); 03023 } else { 03024 // if the transaction was automatic and has done write operations, 03025 // log it if $wgDebugDBTransactions is enabled. 03026 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { 03027 wfDebug( "$fname: Automatic transaction with writes in progress" . 03028 " (from {$this->mTrxFname}), performing implicit commit!\n" ); 03029 } 03030 } 03031 03032 $this->doCommit( $fname ); 03033 $this->runOnTransactionIdleCallbacks(); 03034 } 03035 03036 $this->doBegin( $fname ); 03037 $this->mTrxFname = $fname; 03038 $this->mTrxDoneWrites = false; 03039 $this->mTrxAutomatic = false; 03040 } 03041 03048 protected function doBegin( $fname ) { 03049 $this->query( 'BEGIN', $fname ); 03050 $this->mTrxLevel = 1; 03051 } 03052 03065 final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) { 03066 if ( $flush != 'flush' ) { 03067 if ( !$this->mTrxLevel ) { 03068 wfWarn( "$fname: No transaction to commit, something got out of sync!" ); 03069 } elseif( $this->mTrxAutomatic ) { 03070 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); 03071 } 03072 } else { 03073 if ( !$this->mTrxLevel ) { 03074 return; // nothing to do 03075 } elseif( !$this->mTrxAutomatic ) { 03076 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03077 } 03078 } 03079 03080 $this->doCommit( $fname ); 03081 $this->runOnTransactionIdleCallbacks(); 03082 } 03083 03090 protected function doCommit( $fname ) { 03091 if ( $this->mTrxLevel ) { 03092 $this->query( 'COMMIT', $fname ); 03093 $this->mTrxLevel = 0; 03094 } 03095 } 03096 03105 final public function rollback( $fname = 'DatabaseBase::rollback' ) { 03106 if ( !$this->mTrxLevel ) { 03107 wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); 03108 } 03109 $this->doRollback( $fname ); 03110 $this->mTrxIdleCallbacks = array(); // cancel 03111 } 03112 03119 protected function doRollback( $fname ) { 03120 if ( $this->mTrxLevel ) { 03121 $this->query( 'ROLLBACK', $fname, true ); 03122 $this->mTrxLevel = 0; 03123 } 03124 } 03125 03141 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 03142 $fname = 'DatabaseBase::duplicateTableStructure' ) 03143 { 03144 throw new MWException( 03145 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 03146 } 03147 03155 function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) { 03156 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 03157 } 03158 03170 public function timestamp( $ts = 0 ) { 03171 return wfTimestamp( TS_MW, $ts ); 03172 } 03173 03187 public function timestampOrNull( $ts = null ) { 03188 if ( is_null( $ts ) ) { 03189 return null; 03190 } else { 03191 return $this->timestamp( $ts ); 03192 } 03193 } 03194 03210 public function resultObject( $result ) { 03211 if ( empty( $result ) ) { 03212 return false; 03213 } elseif ( $result instanceof ResultWrapper ) { 03214 return $result; 03215 } elseif ( $result === true ) { 03216 // Successful write query 03217 return $result; 03218 } else { 03219 return new ResultWrapper( $this, $result ); 03220 } 03221 } 03222 03228 public function ping() { 03229 # Stub. Not essential to override. 03230 return true; 03231 } 03232 03242 public function getLag() { 03243 return intval( $this->mFakeSlaveLag ); 03244 } 03245 03251 function maxListLen() { 03252 return 0; 03253 } 03254 03263 public function encodeBlob( $b ) { 03264 return $b; 03265 } 03266 03274 public function decodeBlob( $b ) { 03275 return $b; 03276 } 03277 03288 public function setSessionOptions( array $options ) {} 03289 03306 public function sourceFile( 03307 $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false 03308 ) { 03309 wfSuppressWarnings(); 03310 $fp = fopen( $filename, 'r' ); 03311 wfRestoreWarnings(); 03312 03313 if ( false === $fp ) { 03314 throw new MWException( "Could not open \"{$filename}\".\n" ); 03315 } 03316 03317 if ( !$fname ) { 03318 $fname = __METHOD__ . "( $filename )"; 03319 } 03320 03321 try { 03322 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); 03323 } 03324 catch ( MWException $e ) { 03325 fclose( $fp ); 03326 throw $e; 03327 } 03328 03329 fclose( $fp ); 03330 03331 return $error; 03332 } 03333 03342 public function patchPath( $patch ) { 03343 global $IP; 03344 03345 $dbType = $this->getType(); 03346 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 03347 return "$IP/maintenance/$dbType/archives/$patch"; 03348 } else { 03349 return "$IP/maintenance/archives/$patch"; 03350 } 03351 } 03352 03360 public function setSchemaVars( $vars ) { 03361 $this->mSchemaVars = $vars; 03362 } 03363 03377 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 03378 $fname = 'DatabaseBase::sourceStream', $inputCallback = false ) 03379 { 03380 $cmd = ''; 03381 03382 while ( !feof( $fp ) ) { 03383 if ( $lineCallback ) { 03384 call_user_func( $lineCallback ); 03385 } 03386 03387 $line = trim( fgets( $fp ) ); 03388 03389 if ( $line == '' ) { 03390 continue; 03391 } 03392 03393 if ( '-' == $line[0] && '-' == $line[1] ) { 03394 continue; 03395 } 03396 03397 if ( $cmd != '' ) { 03398 $cmd .= ' '; 03399 } 03400 03401 $done = $this->streamStatementEnd( $cmd, $line ); 03402 03403 $cmd .= "$line\n"; 03404 03405 if ( $done || feof( $fp ) ) { 03406 $cmd = $this->replaceVars( $cmd ); 03407 03408 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { 03409 $res = $this->query( $cmd, $fname ); 03410 03411 if ( $resultCallback ) { 03412 call_user_func( $resultCallback, $res, $this ); 03413 } 03414 03415 if ( false === $res ) { 03416 $err = $this->lastError(); 03417 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 03418 } 03419 } 03420 $cmd = ''; 03421 } 03422 } 03423 03424 return true; 03425 } 03426 03434 public function streamStatementEnd( &$sql, &$newLine ) { 03435 if ( $this->delimiter ) { 03436 $prev = $newLine; 03437 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 03438 if ( $newLine != $prev ) { 03439 return true; 03440 } 03441 } 03442 return false; 03443 } 03444 03462 protected function replaceSchemaVars( $ins ) { 03463 $vars = $this->getSchemaVars(); 03464 foreach ( $vars as $var => $value ) { 03465 // replace '{$var}' 03466 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 03467 // replace `{$var}` 03468 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 03469 // replace /*$var*/ 03470 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); 03471 } 03472 return $ins; 03473 } 03474 03482 protected function replaceVars( $ins ) { 03483 $ins = $this->replaceSchemaVars( $ins ); 03484 03485 // Table prefixes 03486 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 03487 array( $this, 'tableNameCallback' ), $ins ); 03488 03489 // Index names 03490 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 03491 array( $this, 'indexNameCallback' ), $ins ); 03492 03493 return $ins; 03494 } 03495 03502 protected function getSchemaVars() { 03503 if ( $this->mSchemaVars ) { 03504 return $this->mSchemaVars; 03505 } else { 03506 return $this->getDefaultSchemaVars(); 03507 } 03508 } 03509 03518 protected function getDefaultSchemaVars() { 03519 return array(); 03520 } 03521 03529 protected function tableNameCallback( $matches ) { 03530 return $this->tableName( $matches[1] ); 03531 } 03532 03540 protected function indexNameCallback( $matches ) { 03541 return $this->indexName( $matches[1] ); 03542 } 03543 03552 public function lockIsFree( $lockName, $method ) { 03553 return true; 03554 } 03555 03567 public function lock( $lockName, $method, $timeout = 5 ) { 03568 return true; 03569 } 03570 03581 public function unlock( $lockName, $method ) { 03582 return true; 03583 } 03584 03595 public function lockTables( $read, $write, $method, $lowPriority = true ) { 03596 return true; 03597 } 03598 03606 public function unlockTables( $method ) { 03607 return true; 03608 } 03609 03617 public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) { 03618 if( !$this->tableExists( $tableName, $fName ) ) { 03619 return false; 03620 } 03621 $sql = "DROP TABLE " . $this->tableName( $tableName ); 03622 if( $this->cascadingDeletes() ) { 03623 $sql .= " CASCADE"; 03624 } 03625 return $this->query( $sql, $fName ); 03626 } 03627 03634 public function getSearchEngine() { 03635 return 'SearchEngineDummy'; 03636 } 03637 03645 public function getInfinity() { 03646 return 'infinity'; 03647 } 03648 03655 public function encodeExpiry( $expiry ) { 03656 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 03657 ? $this->getInfinity() 03658 : $this->timestamp( $expiry ); 03659 } 03660 03668 public function decodeExpiry( $expiry, $format = TS_MW ) { 03669 return ( $expiry == '' || $expiry == $this->getInfinity() ) 03670 ? 'infinity' 03671 : wfTimestamp( $format, $expiry ); 03672 } 03673 03683 public function setBigSelects( $value = true ) { 03684 // no-op 03685 } 03686 03690 public function __toString() { 03691 return (string)$this->mConn; 03692 } 03693 03694 public function __destruct() { 03695 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 03696 trigger_error( "Transaction idle callbacks still pending." ); 03697 } 03698 } 03699 }