MediaWiki
master
|
00001 <?php 00028 define( 'DEADLOCK_TRIES', 4 ); 00030 define( 'DEADLOCK_DELAY_MIN', 500000 ); 00032 define( 'DEADLOCK_DELAY_MAX', 1500000 ); 00033 00041 interface DatabaseType { 00047 function getType(); 00048 00059 function open( $server, $user, $password, $dbName ); 00060 00070 function fetchObject( $res ); 00071 00080 function fetchRow( $res ); 00081 00088 function numRows( $res ); 00089 00097 function numFields( $res ); 00098 00107 function fieldName( $res, $n ); 00108 00121 function insertId(); 00122 00130 function dataSeek( $res, $row ); 00131 00138 function lastErrno(); 00139 00146 function lastError(); 00147 00157 function fieldInfo( $table, $field ); 00158 00166 function indexInfo( $table, $index, $fname = 'Database::indexInfo' ); 00167 00174 function affectedRows(); 00175 00182 function strencode( $s ); 00183 00192 static function getSoftwareLink(); 00193 00200 function getServerVersion(); 00201 00209 function getServerInfo(); 00210 } 00211 00216 abstract class DatabaseBase implements DatabaseType { 00217 00218 # ------------------------------------------------------------------------------ 00219 # Variables 00220 # ------------------------------------------------------------------------------ 00221 00222 protected $mLastQuery = ''; 00223 protected $mDoneWrites = false; 00224 protected $mPHPError = false; 00225 00226 protected $mServer, $mUser, $mPassword, $mDBname; 00227 00228 protected $mConn = null; 00229 protected $mOpened = false; 00230 00235 protected $trxIdleCallbacks = array(); 00236 00237 protected $mTablePrefix; 00238 protected $mFlags; 00239 protected $mTrxLevel = 0; 00240 protected $mErrorCount = 0; 00241 protected $mLBInfo = array(); 00242 protected $mFakeSlaveLag = null, $mFakeMaster = false; 00243 protected $mDefaultBigSelects = null; 00244 protected $mSchemaVars = false; 00245 00246 protected $preparedArgs; 00247 00248 protected $htmlErrors; 00249 00250 protected $delimiter = ';'; 00251 00259 private $mTrxFname = null; 00260 00267 private $mTrxDoneWrites = false; 00268 00275 private $mTrxAutomatic = false; 00276 00277 # ------------------------------------------------------------------------------ 00278 # Accessors 00279 # ------------------------------------------------------------------------------ 00280 # These optionally set a variable and return the previous state 00281 00289 public function getServerInfo() { 00290 return $this->getServerVersion(); 00291 } 00292 00296 public function getDelimiter() { 00297 return $this->delimiter; 00298 } 00299 00309 public function debug( $debug = null ) { 00310 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 00311 } 00312 00335 public function bufferResults( $buffer = null ) { 00336 if ( is_null( $buffer ) ) { 00337 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 00338 } else { 00339 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 00340 } 00341 } 00342 00354 public function ignoreErrors( $ignoreErrors = null ) { 00355 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 00356 } 00357 00367 public function trxLevel( $level = null ) { 00368 return wfSetVar( $this->mTrxLevel, $level ); 00369 } 00370 00376 public function errorCount( $count = null ) { 00377 return wfSetVar( $this->mErrorCount, $count ); 00378 } 00379 00385 public function tablePrefix( $prefix = null ) { 00386 return wfSetVar( $this->mTablePrefix, $prefix ); 00387 } 00388 00398 public function getLBInfo( $name = null ) { 00399 if ( is_null( $name ) ) { 00400 return $this->mLBInfo; 00401 } else { 00402 if ( array_key_exists( $name, $this->mLBInfo ) ) { 00403 return $this->mLBInfo[$name]; 00404 } else { 00405 return null; 00406 } 00407 } 00408 } 00409 00418 public function setLBInfo( $name, $value = null ) { 00419 if ( is_null( $value ) ) { 00420 $this->mLBInfo = $name; 00421 } else { 00422 $this->mLBInfo[$name] = $value; 00423 } 00424 } 00425 00431 public function setFakeSlaveLag( $lag ) { 00432 $this->mFakeSlaveLag = $lag; 00433 } 00434 00440 public function setFakeMaster( $enabled = true ) { 00441 $this->mFakeMaster = $enabled; 00442 } 00443 00449 public function cascadingDeletes() { 00450 return false; 00451 } 00452 00458 public function cleanupTriggers() { 00459 return false; 00460 } 00461 00468 public function strictIPs() { 00469 return false; 00470 } 00471 00477 public function realTimestamps() { 00478 return false; 00479 } 00480 00486 public function implicitGroupby() { 00487 return true; 00488 } 00489 00496 public function implicitOrderby() { 00497 return true; 00498 } 00499 00506 public function searchableIPs() { 00507 return false; 00508 } 00509 00515 public function functionalIndexes() { 00516 return false; 00517 } 00518 00523 public function lastQuery() { 00524 return $this->mLastQuery; 00525 } 00526 00533 public function doneWrites() { 00534 return $this->mDoneWrites; 00535 } 00536 00541 public function isOpen() { 00542 return $this->mOpened; 00543 } 00544 00557 public function setFlag( $flag ) { 00558 global $wgDebugDBTransactions; 00559 $this->mFlags |= $flag; 00560 if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) { 00561 wfDebug("Implicit transactions are now disabled.\n"); 00562 } 00563 } 00564 00570 public function clearFlag( $flag ) { 00571 global $wgDebugDBTransactions; 00572 $this->mFlags &= ~$flag; 00573 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00574 wfDebug("Implicit transactions are now disabled.\n"); 00575 } 00576 } 00577 00584 public function getFlag( $flag ) { 00585 return !!( $this->mFlags & $flag ); 00586 } 00587 00595 public function getProperty( $name ) { 00596 return $this->$name; 00597 } 00598 00602 public function getWikiID() { 00603 if ( $this->mTablePrefix ) { 00604 return "{$this->mDBname}-{$this->mTablePrefix}"; 00605 } else { 00606 return $this->mDBname; 00607 } 00608 } 00609 00615 public function getSchemaPath() { 00616 global $IP; 00617 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) { 00618 return "$IP/maintenance/" . $this->getType() . "/tables.sql"; 00619 } else { 00620 return "$IP/maintenance/tables.sql"; 00621 } 00622 } 00623 00624 # ------------------------------------------------------------------------------ 00625 # Other functions 00626 # ------------------------------------------------------------------------------ 00627 00637 function __construct( $server = false, $user = false, $password = false, $dbName = false, 00638 $flags = 0, $tablePrefix = 'get from global' 00639 ) { 00640 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions; 00641 00642 $this->mFlags = $flags; 00643 00644 if ( $this->mFlags & DBO_DEFAULT ) { 00645 if ( $wgCommandLineMode ) { 00646 $this->mFlags &= ~DBO_TRX; 00647 if ( $wgDebugDBTransactions ) { 00648 wfDebug("Implicit transaction open disabled.\n"); 00649 } 00650 } else { 00651 $this->mFlags |= DBO_TRX; 00652 if ( $wgDebugDBTransactions ) { 00653 wfDebug("Implicit transaction open enabled.\n"); 00654 } 00655 } 00656 } 00657 00659 if ( $tablePrefix == 'get from global' ) { 00660 $this->mTablePrefix = $wgDBprefix; 00661 } else { 00662 $this->mTablePrefix = $tablePrefix; 00663 } 00664 00665 if ( $user ) { 00666 $this->open( $server, $user, $password, $dbName ); 00667 } 00668 } 00669 00675 public function __sleep() { 00676 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' ); 00677 } 00678 00701 public final static function factory( $dbType, $p = array() ) { 00702 $canonicalDBTypes = array( 00703 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2' 00704 ); 00705 $dbType = strtolower( $dbType ); 00706 $class = 'Database' . ucfirst( $dbType ); 00707 00708 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) { 00709 return new $class( 00710 isset( $p['host'] ) ? $p['host'] : false, 00711 isset( $p['user'] ) ? $p['user'] : false, 00712 isset( $p['password'] ) ? $p['password'] : false, 00713 isset( $p['dbname'] ) ? $p['dbname'] : false, 00714 isset( $p['flags'] ) ? $p['flags'] : 0, 00715 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global' 00716 ); 00717 } else { 00718 return null; 00719 } 00720 } 00721 00722 protected function installErrorHandler() { 00723 $this->mPHPError = false; 00724 $this->htmlErrors = ini_set( 'html_errors', '0' ); 00725 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 00726 } 00727 00731 protected function restoreErrorHandler() { 00732 restore_error_handler(); 00733 if ( $this->htmlErrors !== false ) { 00734 ini_set( 'html_errors', $this->htmlErrors ); 00735 } 00736 if ( $this->mPHPError ) { 00737 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 00738 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 00739 return $error; 00740 } else { 00741 return false; 00742 } 00743 } 00744 00749 protected function connectionErrorHandler( $errno, $errstr ) { 00750 $this->mPHPError = $errstr; 00751 } 00752 00759 public function close() { 00760 $this->mOpened = false; 00761 if ( $this->mConn ) { 00762 if ( $this->trxLevel() ) { 00763 if ( !$this->mTrxAutomatic ) { 00764 wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . 00765 " performing implicit commit before closing connection!" ); 00766 } 00767 00768 $this->commit( __METHOD__, 'flush' ); 00769 } 00770 00771 $ret = $this->closeConnection(); 00772 $this->mConn = false; 00773 return $ret; 00774 } else { 00775 return true; 00776 } 00777 } 00778 00784 protected abstract function closeConnection(); 00785 00790 function reportConnectionError( $error = 'Unknown error' ) { 00791 $myError = $this->lastError(); 00792 if ( $myError ) { 00793 $error = $myError; 00794 } 00795 00796 # New method 00797 throw new DBConnectionError( $this, $error ); 00798 } 00799 00806 protected abstract function doQuery( $sql ); 00807 00816 public function isWriteQuery( $sql ) { 00817 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 00818 } 00819 00842 public function query( $sql, $fname = '', $tempIgnore = false ) { 00843 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 00844 if ( !Profiler::instance()->isStub() ) { 00845 # generalizeSQL will probably cut down the query to reasonable 00846 # logging size most of the time. The substr is really just a sanity check. 00847 00848 if ( $isMaster ) { 00849 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00850 $totalProf = 'DatabaseBase::query-master'; 00851 } else { 00852 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00853 $totalProf = 'DatabaseBase::query'; 00854 } 00855 00856 wfProfileIn( $totalProf ); 00857 wfProfileIn( $queryProf ); 00858 } 00859 00860 $this->mLastQuery = $sql; 00861 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { 00862 # Set a flag indicating that writes have been done 00863 wfDebug( __METHOD__ . ": Writes done: $sql\n" ); 00864 $this->mDoneWrites = true; 00865 } 00866 00867 # Add a comment for easy SHOW PROCESSLIST interpretation 00868 global $wgUser; 00869 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 00870 $userName = $wgUser->getName(); 00871 if ( mb_strlen( $userName ) > 15 ) { 00872 $userName = mb_substr( $userName, 0, 15 ) . '...'; 00873 } 00874 $userName = str_replace( '/', '', $userName ); 00875 } else { 00876 $userName = ''; 00877 } 00878 $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 ); 00879 00880 # If DBO_TRX is set, start a transaction 00881 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && 00882 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) 00883 { 00884 # Avoid establishing transactions for SHOW and SET statements too - 00885 # that would delay transaction initializations to once connection 00886 # is really used by application 00887 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 00888 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 00889 global $wgDebugDBTransactions; 00890 if ( $wgDebugDBTransactions ) { 00891 wfDebug("Implicit transaction start.\n"); 00892 } 00893 $this->begin( __METHOD__ . " ($fname)" ); 00894 $this->mTrxAutomatic = true; 00895 } 00896 } 00897 00898 # Keep track of whether the transaction has write queries pending 00899 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { 00900 $this->mTrxDoneWrites = true; 00901 } 00902 00903 if ( $this->debug() ) { 00904 static $cnt = 0; 00905 00906 $cnt++; 00907 $sqlx = substr( $commentedSql, 0, 500 ); 00908 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 00909 00910 $master = $isMaster ? 'master' : 'slave'; 00911 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 00912 } 00913 00914 if ( istainted( $sql ) & TC_MYSQL ) { 00915 throw new MWException( 'Tainted query found' ); 00916 } 00917 00918 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 00919 00920 # Do the query and handle errors 00921 $ret = $this->doQuery( $commentedSql ); 00922 00923 MWDebug::queryTime( $queryId ); 00924 00925 # Try reconnecting if the connection was lost 00926 if ( false === $ret && $this->wasErrorReissuable() ) { 00927 # Transaction is gone, like it or not 00928 $this->mTrxLevel = 0; 00929 $this->trxIdleCallbacks = array(); // cancel 00930 wfDebug( "Connection lost, reconnecting...\n" ); 00931 00932 if ( $this->ping() ) { 00933 wfDebug( "Reconnected\n" ); 00934 $sqlx = substr( $commentedSql, 0, 500 ); 00935 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 00936 global $wgRequestTime; 00937 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 00938 if ( $elapsed < 300 ) { 00939 # Not a database error to lose a transaction after a minute or two 00940 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); 00941 } 00942 $ret = $this->doQuery( $commentedSql ); 00943 } else { 00944 wfDebug( "Failed\n" ); 00945 } 00946 } 00947 00948 if ( false === $ret ) { 00949 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 00950 } 00951 00952 if ( !Profiler::instance()->isStub() ) { 00953 wfProfileOut( $queryProf ); 00954 wfProfileOut( $totalProf ); 00955 } 00956 00957 return $this->resultObject( $ret ); 00958 } 00959 00971 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 00972 # Ignore errors during error handling to avoid infinite recursion 00973 $ignore = $this->ignoreErrors( true ); 00974 ++$this->mErrorCount; 00975 00976 if ( $ignore || $tempIgnore ) { 00977 wfDebug( "SQL ERROR (ignored): $error\n" ); 00978 $this->ignoreErrors( $ignore ); 00979 } else { 00980 $sql1line = str_replace( "\n", "\\n", $sql ); 00981 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" ); 00982 wfDebug( "SQL ERROR: " . $error . "\n" ); 00983 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 00984 } 00985 } 00986 01001 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 01002 /* MySQL doesn't support prepared statements (yet), so just 01003 pack up the query for reference. We'll manually replace 01004 the bits later. */ 01005 return array( 'query' => $sql, 'func' => $func ); 01006 } 01007 01012 protected function freePrepared( $prepared ) { 01013 /* No-op by default */ 01014 } 01015 01023 public function execute( $prepared, $args = null ) { 01024 if ( !is_array( $args ) ) { 01025 # Pull the var args 01026 $args = func_get_args(); 01027 array_shift( $args ); 01028 } 01029 01030 $sql = $this->fillPrepared( $prepared['query'], $args ); 01031 01032 return $this->query( $sql, $prepared['func'] ); 01033 } 01034 01042 public function fillPrepared( $preparedQuery, $args ) { 01043 reset( $args ); 01044 $this->preparedArgs =& $args; 01045 01046 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 01047 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 01048 } 01049 01059 protected function fillPreparedArg( $matches ) { 01060 switch( $matches[1] ) { 01061 case '\\?': return '?'; 01062 case '\\!': return '!'; 01063 case '\\&': return '&'; 01064 } 01065 01066 list( /* $n */ , $arg ) = each( $this->preparedArgs ); 01067 01068 switch( $matches[1] ) { 01069 case '?': return $this->addQuotes( $arg ); 01070 case '!': return $arg; 01071 case '&': 01072 # return $this->addQuotes( file_get_contents( $arg ) ); 01073 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); 01074 default: 01075 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); 01076 } 01077 } 01078 01086 public function freeResult( $res ) {} 01087 01105 public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField', 01106 $options = array() ) 01107 { 01108 if ( !is_array( $options ) ) { 01109 $options = array( $options ); 01110 } 01111 01112 $options['LIMIT'] = 1; 01113 01114 $res = $this->select( $table, $var, $cond, $fname, $options ); 01115 01116 if ( $res === false || !$this->numRows( $res ) ) { 01117 return false; 01118 } 01119 01120 $row = $this->fetchRow( $res ); 01121 01122 if ( $row !== false ) { 01123 return reset( $row ); 01124 } else { 01125 return false; 01126 } 01127 } 01128 01138 public function makeSelectOptions( $options ) { 01139 $preLimitTail = $postLimitTail = ''; 01140 $startOpts = ''; 01141 01142 $noKeyOptions = array(); 01143 01144 foreach ( $options as $key => $option ) { 01145 if ( is_numeric( $key ) ) { 01146 $noKeyOptions[$option] = true; 01147 } 01148 } 01149 01150 if ( isset( $options['GROUP BY'] ) ) { 01151 $gb = is_array( $options['GROUP BY'] ) 01152 ? implode( ',', $options['GROUP BY'] ) 01153 : $options['GROUP BY']; 01154 $preLimitTail .= " GROUP BY {$gb}"; 01155 } 01156 01157 if ( isset( $options['HAVING'] ) ) { 01158 $having = is_array( $options['HAVING'] ) 01159 ? $this->makeList( $options['HAVING'], LIST_AND ) 01160 : $options['HAVING']; 01161 $preLimitTail .= " HAVING {$having}"; 01162 } 01163 01164 if ( isset( $options['ORDER BY'] ) ) { 01165 $ob = is_array( $options['ORDER BY'] ) 01166 ? implode( ',', $options['ORDER BY'] ) 01167 : $options['ORDER BY']; 01168 $preLimitTail .= " ORDER BY {$ob}"; 01169 } 01170 01171 // if (isset($options['LIMIT'])) { 01172 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 01173 // isset($options['OFFSET']) ? $options['OFFSET'] 01174 // : false); 01175 // } 01176 01177 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 01178 $postLimitTail .= ' FOR UPDATE'; 01179 } 01180 01181 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 01182 $postLimitTail .= ' LOCK IN SHARE MODE'; 01183 } 01184 01185 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01186 $startOpts .= 'DISTINCT'; 01187 } 01188 01189 # Various MySQL extensions 01190 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 01191 $startOpts .= ' /*! STRAIGHT_JOIN */'; 01192 } 01193 01194 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 01195 $startOpts .= ' HIGH_PRIORITY'; 01196 } 01197 01198 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 01199 $startOpts .= ' SQL_BIG_RESULT'; 01200 } 01201 01202 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 01203 $startOpts .= ' SQL_BUFFER_RESULT'; 01204 } 01205 01206 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 01207 $startOpts .= ' SQL_SMALL_RESULT'; 01208 } 01209 01210 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 01211 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 01212 } 01213 01214 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 01215 $startOpts .= ' SQL_CACHE'; 01216 } 01217 01218 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 01219 $startOpts .= ' SQL_NO_CACHE'; 01220 } 01221 01222 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { 01223 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01224 } else { 01225 $useIndex = ''; 01226 } 01227 01228 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01229 } 01230 01370 public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', 01371 $options = array(), $join_conds = array() ) { 01372 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 01373 01374 return $this->query( $sql, $fname ); 01375 } 01376 01393 public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', 01394 $options = array(), $join_conds = array() ) 01395 { 01396 if ( is_array( $vars ) ) { 01397 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 01398 } 01399 01400 $options = (array)$options; 01401 01402 if ( is_array( $table ) ) { 01403 $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 01404 ? $options['USE INDEX'] 01405 : array(); 01406 if ( count( $join_conds ) || count( $useIndex ) ) { 01407 $from = ' FROM ' . 01408 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds ); 01409 } else { 01410 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) ); 01411 } 01412 } elseif ( $table != '' ) { 01413 if ( $table[0] == ' ' ) { 01414 $from = ' FROM ' . $table; 01415 } else { 01416 $from = ' FROM ' . $this->tableName( $table ); 01417 } 01418 } else { 01419 $from = ''; 01420 } 01421 01422 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options ); 01423 01424 if ( !empty( $conds ) ) { 01425 if ( is_array( $conds ) ) { 01426 $conds = $this->makeList( $conds, LIST_AND ); 01427 } 01428 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 01429 } else { 01430 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 01431 } 01432 01433 if ( isset( $options['LIMIT'] ) ) { 01434 $sql = $this->limitResult( $sql, $options['LIMIT'], 01435 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 01436 } 01437 $sql = "$sql $postLimitTail"; 01438 01439 if ( isset( $options['EXPLAIN'] ) ) { 01440 $sql = 'EXPLAIN ' . $sql; 01441 } 01442 01443 return $sql; 01444 } 01445 01460 public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow', 01461 $options = array(), $join_conds = array() ) 01462 { 01463 $options = (array)$options; 01464 $options['LIMIT'] = 1; 01465 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 01466 01467 if ( $res === false ) { 01468 return false; 01469 } 01470 01471 if ( !$this->numRows( $res ) ) { 01472 return false; 01473 } 01474 01475 $obj = $this->fetchObject( $res ); 01476 01477 return $obj; 01478 } 01479 01500 public function estimateRowCount( $table, $vars = '*', $conds = '', 01501 $fname = 'DatabaseBase::estimateRowCount', $options = array() ) 01502 { 01503 $rows = 0; 01504 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 01505 01506 if ( $res ) { 01507 $row = $this->fetchRow( $res ); 01508 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01509 } 01510 01511 return $rows; 01512 } 01513 01522 static function generalizeSQL( $sql ) { 01523 # This does the same as the regexp below would do, but in such a way 01524 # as to avoid crashing php on some large strings. 01525 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); 01526 01527 $sql = str_replace ( "\\\\", '', $sql ); 01528 $sql = str_replace ( "\\'", '', $sql ); 01529 $sql = str_replace ( "\\\"", '', $sql ); 01530 $sql = preg_replace ( "/'.*'/s", "'X'", $sql ); 01531 $sql = preg_replace ( '/".*"/s', "'X'", $sql ); 01532 01533 # All newlines, tabs, etc replaced by single space 01534 $sql = preg_replace ( '/\s+/', ' ', $sql ); 01535 01536 # All numbers => N 01537 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql ); 01538 01539 return $sql; 01540 } 01541 01550 public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) { 01551 $info = $this->fieldInfo( $table, $field ); 01552 01553 return (bool)$info; 01554 } 01555 01567 public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) { 01568 $info = $this->indexInfo( $table, $index, $fname ); 01569 if ( is_null( $info ) ) { 01570 return null; 01571 } else { 01572 return $info !== false; 01573 } 01574 } 01575 01584 public function tableExists( $table, $fname = __METHOD__ ) { 01585 $table = $this->tableName( $table ); 01586 $old = $this->ignoreErrors( true ); 01587 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 01588 $this->ignoreErrors( $old ); 01589 01590 return (bool)$res; 01591 } 01592 01599 public function fieldType( $res, $index ) { 01600 if ( $res instanceof ResultWrapper ) { 01601 $res = $res->result; 01602 } 01603 01604 return mysql_field_type( $res, $index ); 01605 } 01606 01615 public function indexUnique( $table, $index ) { 01616 $indexInfo = $this->indexInfo( $table, $index ); 01617 01618 if ( !$indexInfo ) { 01619 return null; 01620 } 01621 01622 return !$indexInfo[0]->Non_unique; 01623 } 01624 01631 protected function makeInsertOptions( $options ) { 01632 return implode( ' ', $options ); 01633 } 01634 01668 public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) { 01669 # No rows to insert, easy just return now 01670 if ( !count( $a ) ) { 01671 return true; 01672 } 01673 01674 $table = $this->tableName( $table ); 01675 01676 if ( !is_array( $options ) ) { 01677 $options = array( $options ); 01678 } 01679 01680 $options = $this->makeInsertOptions( $options ); 01681 01682 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 01683 $multi = true; 01684 $keys = array_keys( $a[0] ); 01685 } else { 01686 $multi = false; 01687 $keys = array_keys( $a ); 01688 } 01689 01690 $sql = 'INSERT ' . $options . 01691 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 01692 01693 if ( $multi ) { 01694 $first = true; 01695 foreach ( $a as $row ) { 01696 if ( $first ) { 01697 $first = false; 01698 } else { 01699 $sql .= ','; 01700 } 01701 $sql .= '(' . $this->makeList( $row ) . ')'; 01702 } 01703 } else { 01704 $sql .= '(' . $this->makeList( $a ) . ')'; 01705 } 01706 01707 return (bool)$this->query( $sql, $fname ); 01708 } 01709 01716 protected function makeUpdateOptions( $options ) { 01717 if ( !is_array( $options ) ) { 01718 $options = array( $options ); 01719 } 01720 01721 $opts = array(); 01722 01723 if ( in_array( 'LOW_PRIORITY', $options ) ) { 01724 $opts[] = $this->lowPriorityOption(); 01725 } 01726 01727 if ( in_array( 'IGNORE', $options ) ) { 01728 $opts[] = 'IGNORE'; 01729 } 01730 01731 return implode( ' ', $opts ); 01732 } 01733 01757 function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) { 01758 $table = $this->tableName( $table ); 01759 $opts = $this->makeUpdateOptions( $options ); 01760 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 01761 01762 if ( $conds !== array() && $conds !== '*' ) { 01763 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 01764 } 01765 01766 return $this->query( $sql, $fname ); 01767 } 01768 01783 public function makeList( $a, $mode = LIST_COMMA ) { 01784 if ( !is_array( $a ) ) { 01785 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 01786 } 01787 01788 $first = true; 01789 $list = ''; 01790 01791 foreach ( $a as $field => $value ) { 01792 if ( !$first ) { 01793 if ( $mode == LIST_AND ) { 01794 $list .= ' AND '; 01795 } elseif ( $mode == LIST_OR ) { 01796 $list .= ' OR '; 01797 } else { 01798 $list .= ','; 01799 } 01800 } else { 01801 $first = false; 01802 } 01803 01804 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 01805 $list .= "($value)"; 01806 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 01807 $list .= "$value"; 01808 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 01809 if ( count( $value ) == 0 ) { 01810 throw new MWException( __METHOD__ . ": empty input for field $field" ); 01811 } elseif ( count( $value ) == 1 ) { 01812 // Special-case single values, as IN isn't terribly efficient 01813 // Don't necessarily assume the single key is 0; we don't 01814 // enforce linear numeric ordering on other arrays here. 01815 $value = array_values( $value ); 01816 $list .= $field . " = " . $this->addQuotes( $value[0] ); 01817 } else { 01818 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 01819 } 01820 } elseif ( $value === null ) { 01821 if ( $mode == LIST_AND || $mode == LIST_OR ) { 01822 $list .= "$field IS "; 01823 } elseif ( $mode == LIST_SET ) { 01824 $list .= "$field = "; 01825 } 01826 $list .= 'NULL'; 01827 } else { 01828 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 01829 $list .= "$field = "; 01830 } 01831 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 01832 } 01833 } 01834 01835 return $list; 01836 } 01837 01848 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 01849 $conds = array(); 01850 01851 foreach ( $data as $base => $sub ) { 01852 if ( count( $sub ) ) { 01853 $conds[] = $this->makeList( 01854 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 01855 LIST_AND ); 01856 } 01857 } 01858 01859 if ( $conds ) { 01860 return $this->makeList( $conds, LIST_OR ); 01861 } else { 01862 // Nothing to search for... 01863 return false; 01864 } 01865 } 01866 01875 public function aggregateValue( $valuedata, $valuename = 'value' ) { 01876 return $valuename; 01877 } 01878 01883 public function bitNot( $field ) { 01884 return "(~$field)"; 01885 } 01886 01892 public function bitAnd( $fieldLeft, $fieldRight ) { 01893 return "($fieldLeft & $fieldRight)"; 01894 } 01895 01901 public function bitOr( $fieldLeft, $fieldRight ) { 01902 return "($fieldLeft | $fieldRight)"; 01903 } 01904 01910 public function buildConcat( $stringList ) { 01911 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 01912 } 01913 01923 public function selectDB( $db ) { 01924 # Stub. Shouldn't cause serious problems if it's not overridden, but 01925 # if your database engine supports a concept similar to MySQL's 01926 # databases you may as well. 01927 $this->mDBname = $db; 01928 return true; 01929 } 01930 01934 public function getDBname() { 01935 return $this->mDBname; 01936 } 01937 01941 public function getServer() { 01942 return $this->mServer; 01943 } 01944 01962 public function tableName( $name, $format = 'quoted' ) { 01963 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables; 01964 # Skip the entire process when we have a string quoted on both ends. 01965 # Note that we check the end so that we will still quote any use of 01966 # use of `database`.table. But won't break things if someone wants 01967 # to query a database table with a dot in the name. 01968 if ( $this->isQuotedIdentifier( $name ) ) { 01969 return $name; 01970 } 01971 01972 # Lets test for any bits of text that should never show up in a table 01973 # name. Basically anything like JOIN or ON which are actually part of 01974 # SQL queries, but may end up inside of the table value to combine 01975 # sql. Such as how the API is doing. 01976 # Note that we use a whitespace test rather than a \b test to avoid 01977 # any remote case where a word like on may be inside of a table name 01978 # surrounded by symbols which may be considered word breaks. 01979 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 01980 return $name; 01981 } 01982 01983 # Split database and table into proper variables. 01984 # We reverse the explode so that database.table and table both output 01985 # the correct table. 01986 $dbDetails = array_reverse( explode( '.', $name, 2 ) ); 01987 if ( isset( $dbDetails[1] ) ) { 01988 list( $table, $database ) = $dbDetails; 01989 } else { 01990 list( $table ) = $dbDetails; 01991 } 01992 $prefix = $this->mTablePrefix; # Default prefix 01993 01994 # A database name has been specified in input. We don't want any 01995 # prefixes added. 01996 if ( isset( $database ) ) { 01997 $prefix = ''; 01998 } 01999 02000 # Note that we use the long format because php will complain in in_array if 02001 # the input is not an array, and will complain in is_array if it is not set. 02002 if ( !isset( $database ) # Don't use shared database if pre selected. 02003 && isset( $wgSharedDB ) # We have a shared database 02004 && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' 02005 && isset( $wgSharedTables ) 02006 && is_array( $wgSharedTables ) 02007 && in_array( $table, $wgSharedTables ) ) { # A shared table is selected 02008 $database = $wgSharedDB; 02009 $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix; 02010 } 02011 02012 # Quote the $database and $table and apply the prefix if not quoted. 02013 if ( isset( $database ) ) { 02014 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 02015 $database = $this->addIdentifierQuotes( $database ); 02016 } 02017 } 02018 02019 $table = "{$prefix}{$table}"; 02020 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) { 02021 $table = $this->addIdentifierQuotes( "{$table}" ); 02022 } 02023 02024 # Merge our database and table into our final table name. 02025 $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" ); 02026 02027 return $tableName; 02028 } 02029 02041 public function tableNames() { 02042 $inArray = func_get_args(); 02043 $retVal = array(); 02044 02045 foreach ( $inArray as $name ) { 02046 $retVal[$name] = $this->tableName( $name ); 02047 } 02048 02049 return $retVal; 02050 } 02051 02063 public function tableNamesN() { 02064 $inArray = func_get_args(); 02065 $retVal = array(); 02066 02067 foreach ( $inArray as $name ) { 02068 $retVal[] = $this->tableName( $name ); 02069 } 02070 02071 return $retVal; 02072 } 02073 02082 public function tableNameWithAlias( $name, $alias = false ) { 02083 if ( !$alias || $alias == $name ) { 02084 return $this->tableName( $name ); 02085 } else { 02086 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 02087 } 02088 } 02089 02096 public function tableNamesWithAlias( $tables ) { 02097 $retval = array(); 02098 foreach ( $tables as $alias => $table ) { 02099 if ( is_numeric( $alias ) ) { 02100 $alias = $table; 02101 } 02102 $retval[] = $this->tableNameWithAlias( $table, $alias ); 02103 } 02104 return $retval; 02105 } 02106 02115 public function fieldNameWithAlias( $name, $alias = false ) { 02116 if ( !$alias || (string)$alias === (string)$name ) { 02117 return $name; 02118 } else { 02119 return $name . ' AS ' . $alias; //PostgreSQL needs AS 02120 } 02121 } 02122 02129 public function fieldNamesWithAlias( $fields ) { 02130 $retval = array(); 02131 foreach ( $fields as $alias => $field ) { 02132 if ( is_numeric( $alias ) ) { 02133 $alias = $field; 02134 } 02135 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 02136 } 02137 return $retval; 02138 } 02139 02149 protected function tableNamesWithUseIndexOrJOIN( 02150 $tables, $use_index = array(), $join_conds = array() 02151 ) { 02152 $ret = array(); 02153 $retJOIN = array(); 02154 $use_index = (array)$use_index; 02155 $join_conds = (array)$join_conds; 02156 02157 foreach ( $tables as $alias => $table ) { 02158 if ( !is_string( $alias ) ) { 02159 // No alias? Set it equal to the table name 02160 $alias = $table; 02161 } 02162 // Is there a JOIN clause for this table? 02163 if ( isset( $join_conds[$alias] ) ) { 02164 list( $joinType, $conds ) = $join_conds[$alias]; 02165 $tableClause = $joinType; 02166 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 02167 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 02168 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 02169 if ( $use != '' ) { 02170 $tableClause .= ' ' . $use; 02171 } 02172 } 02173 $on = $this->makeList( (array)$conds, LIST_AND ); 02174 if ( $on != '' ) { 02175 $tableClause .= ' ON (' . $on . ')'; 02176 } 02177 02178 $retJOIN[] = $tableClause; 02179 // Is there an INDEX clause for this table? 02180 } elseif ( isset( $use_index[$alias] ) ) { 02181 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02182 $tableClause .= ' ' . $this->useIndexClause( 02183 implode( ',', (array)$use_index[$alias] ) ); 02184 02185 $ret[] = $tableClause; 02186 } else { 02187 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02188 02189 $ret[] = $tableClause; 02190 } 02191 } 02192 02193 // We can't separate explicit JOIN clauses with ',', use ' ' for those 02194 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 02195 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 02196 02197 // Compile our final table clause 02198 return implode( ' ', array( $straightJoins, $otherJoins ) ); 02199 } 02200 02208 protected function indexName( $index ) { 02209 // Backwards-compatibility hack 02210 $renamed = array( 02211 'ar_usertext_timestamp' => 'usertext_timestamp', 02212 'un_user_id' => 'user_id', 02213 'un_user_ip' => 'user_ip', 02214 ); 02215 02216 if ( isset( $renamed[$index] ) ) { 02217 return $renamed[$index]; 02218 } else { 02219 return $index; 02220 } 02221 } 02222 02231 public function addQuotes( $s ) { 02232 if ( $s === null ) { 02233 return 'NULL'; 02234 } else { 02235 # This will also quote numeric values. This should be harmless, 02236 # and protects against weird problems that occur when they really 02237 # _are_ strings such as article titles and string->number->string 02238 # conversion is not 1:1. 02239 return "'" . $this->strencode( $s ) . "'"; 02240 } 02241 } 02242 02253 public function addIdentifierQuotes( $s ) { 02254 return '"' . str_replace( '"', '""', $s ) . '"'; 02255 } 02256 02265 public function isQuotedIdentifier( $name ) { 02266 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 02267 } 02268 02273 protected function escapeLikeInternal( $s ) { 02274 $s = str_replace( '\\', '\\\\', $s ); 02275 $s = $this->strencode( $s ); 02276 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s ); 02277 02278 return $s; 02279 } 02280 02293 public function buildLike() { 02294 $params = func_get_args(); 02295 02296 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 02297 $params = $params[0]; 02298 } 02299 02300 $s = ''; 02301 02302 foreach ( $params as $value ) { 02303 if ( $value instanceof LikeMatch ) { 02304 $s .= $value->toString(); 02305 } else { 02306 $s .= $this->escapeLikeInternal( $value ); 02307 } 02308 } 02309 02310 return " LIKE '" . $s . "' "; 02311 } 02312 02318 public function anyChar() { 02319 return new LikeMatch( '_' ); 02320 } 02321 02327 public function anyString() { 02328 return new LikeMatch( '%' ); 02329 } 02330 02342 public function nextSequenceValue( $seqName ) { 02343 return null; 02344 } 02345 02356 public function useIndexClause( $index ) { 02357 return ''; 02358 } 02359 02382 public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) { 02383 $quotedTable = $this->tableName( $table ); 02384 02385 if ( count( $rows ) == 0 ) { 02386 return; 02387 } 02388 02389 # Single row case 02390 if ( !is_array( reset( $rows ) ) ) { 02391 $rows = array( $rows ); 02392 } 02393 02394 foreach( $rows as $row ) { 02395 # Delete rows which collide 02396 if ( $uniqueIndexes ) { 02397 $sql = "DELETE FROM $quotedTable WHERE "; 02398 $first = true; 02399 foreach ( $uniqueIndexes as $index ) { 02400 if ( $first ) { 02401 $first = false; 02402 $sql .= '( '; 02403 } else { 02404 $sql .= ' ) OR ( '; 02405 } 02406 if ( is_array( $index ) ) { 02407 $first2 = true; 02408 foreach ( $index as $col ) { 02409 if ( $first2 ) { 02410 $first2 = false; 02411 } else { 02412 $sql .= ' AND '; 02413 } 02414 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 02415 } 02416 } else { 02417 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 02418 } 02419 } 02420 $sql .= ' )'; 02421 $this->query( $sql, $fname ); 02422 } 02423 02424 # Now insert the row 02425 $this->insert( $table, $row ); 02426 } 02427 } 02428 02439 protected function nativeReplace( $table, $rows, $fname ) { 02440 $table = $this->tableName( $table ); 02441 02442 # Single row case 02443 if ( !is_array( reset( $rows ) ) ) { 02444 $rows = array( $rows ); 02445 } 02446 02447 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 02448 $first = true; 02449 02450 foreach ( $rows as $row ) { 02451 if ( $first ) { 02452 $first = false; 02453 } else { 02454 $sql .= ','; 02455 } 02456 02457 $sql .= '(' . $this->makeList( $row ) . ')'; 02458 } 02459 02460 return $this->query( $sql, $fname ); 02461 } 02462 02484 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 02485 $fname = 'DatabaseBase::deleteJoin' ) 02486 { 02487 if ( !$conds ) { 02488 throw new DBUnexpectedError( $this, 02489 'DatabaseBase::deleteJoin() called with empty $conds' ); 02490 } 02491 02492 $delTable = $this->tableName( $delTable ); 02493 $joinTable = $this->tableName( $joinTable ); 02494 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 02495 if ( $conds != '*' ) { 02496 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 02497 } 02498 $sql .= ')'; 02499 02500 $this->query( $sql, $fname ); 02501 } 02502 02511 public function textFieldSize( $table, $field ) { 02512 $table = $this->tableName( $table ); 02513 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 02514 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 02515 $row = $this->fetchObject( $res ); 02516 02517 $m = array(); 02518 02519 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 02520 $size = $m[1]; 02521 } else { 02522 $size = -1; 02523 } 02524 02525 return $size; 02526 } 02527 02536 public function lowPriorityOption() { 02537 return ''; 02538 } 02539 02551 public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) { 02552 if ( !$conds ) { 02553 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 02554 } 02555 02556 $table = $this->tableName( $table ); 02557 $sql = "DELETE FROM $table"; 02558 02559 if ( $conds != '*' ) { 02560 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 02561 } 02562 02563 return $this->query( $sql, $fname ); 02564 } 02565 02592 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 02593 $fname = 'DatabaseBase::insertSelect', 02594 $insertOptions = array(), $selectOptions = array() ) 02595 { 02596 $destTable = $this->tableName( $destTable ); 02597 02598 if ( is_array( $insertOptions ) ) { 02599 $insertOptions = implode( ' ', $insertOptions ); 02600 } 02601 02602 if ( !is_array( $selectOptions ) ) { 02603 $selectOptions = array( $selectOptions ); 02604 } 02605 02606 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 02607 02608 if ( is_array( $srcTable ) ) { 02609 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 02610 } else { 02611 $srcTable = $this->tableName( $srcTable ); 02612 } 02613 02614 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 02615 " SELECT $startOpts " . implode( ',', $varMap ) . 02616 " FROM $srcTable $useIndex "; 02617 02618 if ( $conds != '*' ) { 02619 if ( is_array( $conds ) ) { 02620 $conds = $this->makeList( $conds, LIST_AND ); 02621 } 02622 $sql .= " WHERE $conds"; 02623 } 02624 02625 $sql .= " $tailOpts"; 02626 02627 return $this->query( $sql, $fname ); 02628 } 02629 02650 public function limitResult( $sql, $limit, $offset = false ) { 02651 if ( !is_numeric( $limit ) ) { 02652 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 02653 } 02654 return "$sql LIMIT " 02655 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 02656 . "{$limit} "; 02657 } 02658 02664 public function unionSupportsOrderAndLimit() { 02665 return true; // True for almost every DB supported 02666 } 02667 02676 public function unionQueries( $sqls, $all ) { 02677 $glue = $all ? ') UNION ALL (' : ') UNION ('; 02678 return '(' . implode( $glue, $sqls ) . ')'; 02679 } 02680 02690 public function conditional( $cond, $trueVal, $falseVal ) { 02691 if ( is_array( $cond ) ) { 02692 $cond = $this->makeList( $cond, LIST_AND ); 02693 } 02694 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 02695 } 02696 02707 public function strreplace( $orig, $old, $new ) { 02708 return "REPLACE({$orig}, {$old}, {$new})"; 02709 } 02710 02717 public function getServerUptime() { 02718 return 0; 02719 } 02720 02727 public function wasDeadlock() { 02728 return false; 02729 } 02730 02737 public function wasLockTimeout() { 02738 return false; 02739 } 02740 02748 public function wasErrorReissuable() { 02749 return false; 02750 } 02751 02758 public function wasReadOnlyError() { 02759 return false; 02760 } 02761 02780 public function deadlockLoop() { 02781 $this->begin( __METHOD__ ); 02782 $args = func_get_args(); 02783 $function = array_shift( $args ); 02784 $oldIgnore = $this->ignoreErrors( true ); 02785 $tries = DEADLOCK_TRIES; 02786 02787 if ( is_array( $function ) ) { 02788 $fname = $function[0]; 02789 } else { 02790 $fname = $function; 02791 } 02792 02793 do { 02794 $retVal = call_user_func_array( $function, $args ); 02795 $error = $this->lastError(); 02796 $errno = $this->lastErrno(); 02797 $sql = $this->lastQuery(); 02798 02799 if ( $errno ) { 02800 if ( $this->wasDeadlock() ) { 02801 # Retry 02802 usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) ); 02803 } else { 02804 $this->reportQueryError( $error, $errno, $sql, $fname ); 02805 } 02806 } 02807 } while ( $this->wasDeadlock() && --$tries > 0 ); 02808 02809 $this->ignoreErrors( $oldIgnore ); 02810 02811 if ( $tries <= 0 ) { 02812 $this->rollback( __METHOD__ ); 02813 $this->reportQueryError( $error, $errno, $sql, $fname ); 02814 return false; 02815 } else { 02816 $this->commit( __METHOD__ ); 02817 return $retVal; 02818 } 02819 } 02820 02832 public function masterPosWait( DBMasterPos $pos, $timeout ) { 02833 wfProfileIn( __METHOD__ ); 02834 02835 if ( !is_null( $this->mFakeSlaveLag ) ) { 02836 $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 ); 02837 02838 if ( $wait > $timeout * 1e6 ) { 02839 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" ); 02840 wfProfileOut( __METHOD__ ); 02841 return -1; 02842 } elseif ( $wait > 0 ) { 02843 wfDebug( "Fake slave waiting $wait us\n" ); 02844 usleep( $wait ); 02845 wfProfileOut( __METHOD__ ); 02846 return 1; 02847 } else { 02848 wfDebug( "Fake slave up to date ($wait us)\n" ); 02849 wfProfileOut( __METHOD__ ); 02850 return 0; 02851 } 02852 } 02853 02854 wfProfileOut( __METHOD__ ); 02855 02856 # Real waits are implemented in the subclass. 02857 return 0; 02858 } 02859 02865 public function getSlavePos() { 02866 if ( !is_null( $this->mFakeSlaveLag ) ) { 02867 $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag ); 02868 wfDebug( __METHOD__ . ": fake slave pos = $pos\n" ); 02869 return $pos; 02870 } else { 02871 # Stub 02872 return false; 02873 } 02874 } 02875 02881 public function getMasterPos() { 02882 if ( $this->mFakeMaster ) { 02883 return new MySQLMasterPos( 'fake', microtime( true ) ); 02884 } else { 02885 return false; 02886 } 02887 } 02888 02900 final public function onTransactionIdle( Closure $callback ) { 02901 if ( $this->mTrxLevel ) { 02902 $this->trxIdleCallbacks[] = $callback; 02903 } else { 02904 $callback(); 02905 } 02906 } 02907 02913 protected function runOnTransactionIdleCallbacks() { 02914 $e = null; // last exception 02915 do { // callbacks may add callbacks :) 02916 $callbacks = $this->trxIdleCallbacks; 02917 $this->trxIdleCallbacks = array(); // recursion guard 02918 foreach ( $callbacks as $callback ) { 02919 try { 02920 $callback(); 02921 } catch ( Exception $e ) {} 02922 } 02923 } while ( count( $this->trxIdleCallbacks ) ); 02924 02925 if ( $e instanceof Exception ) { 02926 throw $e; // re-throw any last exception 02927 } 02928 } 02929 02942 final public function begin( $fname = 'DatabaseBase::begin' ) { 02943 global $wgDebugDBTransactions; 02944 02945 if ( $this->mTrxLevel ) { // implicit commit 02946 if ( !$this->mTrxAutomatic ) { 02947 // We want to warn about inadvertently nested begin/commit pairs, but not about auto-committing 02948 // implicit transactions that were started by query() because DBO_TRX was set. 02949 02950 wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " . 02951 " performing implicit commit!" ); 02952 } else { 02953 // if the transaction was automatic and has done write operations, 02954 // log it if $wgDebugDBTransactions is enabled. 02955 02956 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { 02957 wfDebug( "$fname: Automatic transaction with writes in progress (from {$this->mTrxFname}), " . 02958 " performing implicit commit!\n" ); 02959 } 02960 } 02961 02962 $this->doCommit( $fname ); 02963 $this->runOnTransactionIdleCallbacks(); 02964 } 02965 02966 $this->doBegin( $fname ); 02967 $this->mTrxFname = $fname; 02968 $this->mTrxDoneWrites = false; 02969 $this->mTrxAutomatic = false; 02970 } 02971 02978 protected function doBegin( $fname ) { 02979 $this->query( 'BEGIN', $fname ); 02980 $this->mTrxLevel = 1; 02981 } 02982 02995 final public function commit( $fname = 'DatabaseBase::commit', $flush = '' ) { 02996 if ( $flush != 'flush' ) { 02997 if ( !$this->mTrxLevel ) { 02998 wfWarn( "$fname: No transaction to commit, something got out of sync!" ); 02999 } elseif( $this->mTrxAutomatic ) { 03000 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); 03001 } 03002 } else { 03003 if ( !$this->mTrxLevel ) { 03004 return; // nothing to do 03005 } elseif( !$this->mTrxAutomatic ) { 03006 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03007 } 03008 } 03009 03010 $this->doCommit( $fname ); 03011 $this->runOnTransactionIdleCallbacks(); 03012 } 03013 03020 protected function doCommit( $fname ) { 03021 if ( $this->mTrxLevel ) { 03022 $this->query( 'COMMIT', $fname ); 03023 $this->mTrxLevel = 0; 03024 } 03025 } 03026 03035 final public function rollback( $fname = 'DatabaseBase::rollback' ) { 03036 if ( !$this->mTrxLevel ) { 03037 wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); 03038 } 03039 $this->doRollback( $fname ); 03040 $this->trxIdleCallbacks = array(); // cancel 03041 } 03042 03049 protected function doRollback( $fname ) { 03050 if ( $this->mTrxLevel ) { 03051 $this->query( 'ROLLBACK', $fname, true ); 03052 $this->mTrxLevel = 0; 03053 } 03054 } 03055 03071 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 03072 $fname = 'DatabaseBase::duplicateTableStructure' ) 03073 { 03074 throw new MWException( 03075 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 03076 } 03077 03085 function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) { 03086 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 03087 } 03088 03100 public function timestamp( $ts = 0 ) { 03101 return wfTimestamp( TS_MW, $ts ); 03102 } 03103 03117 public function timestampOrNull( $ts = null ) { 03118 if ( is_null( $ts ) ) { 03119 return null; 03120 } else { 03121 return $this->timestamp( $ts ); 03122 } 03123 } 03124 03140 public function resultObject( $result ) { 03141 if ( empty( $result ) ) { 03142 return false; 03143 } elseif ( $result instanceof ResultWrapper ) { 03144 return $result; 03145 } elseif ( $result === true ) { 03146 // Successful write query 03147 return $result; 03148 } else { 03149 return new ResultWrapper( $this, $result ); 03150 } 03151 } 03152 03158 public function ping() { 03159 # Stub. Not essential to override. 03160 return true; 03161 } 03162 03172 public function getLag() { 03173 return intval( $this->mFakeSlaveLag ); 03174 } 03175 03181 function maxListLen() { 03182 return 0; 03183 } 03184 03193 public function encodeBlob( $b ) { 03194 return $b; 03195 } 03196 03204 public function decodeBlob( $b ) { 03205 return $b; 03206 } 03207 03218 public function setSessionOptions( array $options ) {} 03219 03234 public function sourceFile( 03235 $filename, $lineCallback = false, $resultCallback = false, $fname = false 03236 ) { 03237 wfSuppressWarnings(); 03238 $fp = fopen( $filename, 'r' ); 03239 wfRestoreWarnings(); 03240 03241 if ( false === $fp ) { 03242 throw new MWException( "Could not open \"{$filename}\".\n" ); 03243 } 03244 03245 if ( !$fname ) { 03246 $fname = __METHOD__ . "( $filename )"; 03247 } 03248 03249 try { 03250 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname ); 03251 } 03252 catch ( MWException $e ) { 03253 fclose( $fp ); 03254 throw $e; 03255 } 03256 03257 fclose( $fp ); 03258 03259 return $error; 03260 } 03261 03270 public function patchPath( $patch ) { 03271 global $IP; 03272 03273 $dbType = $this->getType(); 03274 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 03275 return "$IP/maintenance/$dbType/archives/$patch"; 03276 } else { 03277 return "$IP/maintenance/archives/$patch"; 03278 } 03279 } 03280 03288 public function setSchemaVars( $vars ) { 03289 $this->mSchemaVars = $vars; 03290 } 03291 03305 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 03306 $fname = 'DatabaseBase::sourceStream', $inputCallback = false ) 03307 { 03308 $cmd = ''; 03309 03310 while ( !feof( $fp ) ) { 03311 if ( $lineCallback ) { 03312 call_user_func( $lineCallback ); 03313 } 03314 03315 $line = trim( fgets( $fp ) ); 03316 03317 if ( $line == '' ) { 03318 continue; 03319 } 03320 03321 if ( '-' == $line[0] && '-' == $line[1] ) { 03322 continue; 03323 } 03324 03325 if ( $cmd != '' ) { 03326 $cmd .= ' '; 03327 } 03328 03329 $done = $this->streamStatementEnd( $cmd, $line ); 03330 03331 $cmd .= "$line\n"; 03332 03333 if ( $done || feof( $fp ) ) { 03334 $cmd = $this->replaceVars( $cmd ); 03335 if ( $inputCallback ) { 03336 call_user_func( $inputCallback, $cmd ); 03337 } 03338 $res = $this->query( $cmd, $fname ); 03339 03340 if ( $resultCallback ) { 03341 call_user_func( $resultCallback, $res, $this ); 03342 } 03343 03344 if ( false === $res ) { 03345 $err = $this->lastError(); 03346 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 03347 } 03348 03349 $cmd = ''; 03350 } 03351 } 03352 03353 return true; 03354 } 03355 03363 public function streamStatementEnd( &$sql, &$newLine ) { 03364 if ( $this->delimiter ) { 03365 $prev = $newLine; 03366 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 03367 if ( $newLine != $prev ) { 03368 return true; 03369 } 03370 } 03371 return false; 03372 } 03373 03391 protected function replaceSchemaVars( $ins ) { 03392 $vars = $this->getSchemaVars(); 03393 foreach ( $vars as $var => $value ) { 03394 // replace '{$var}' 03395 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 03396 // replace `{$var}` 03397 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 03398 // replace /*$var*/ 03399 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins ); 03400 } 03401 return $ins; 03402 } 03403 03411 protected function replaceVars( $ins ) { 03412 $ins = $this->replaceSchemaVars( $ins ); 03413 03414 // Table prefixes 03415 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 03416 array( $this, 'tableNameCallback' ), $ins ); 03417 03418 // Index names 03419 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 03420 array( $this, 'indexNameCallback' ), $ins ); 03421 03422 return $ins; 03423 } 03424 03431 protected function getSchemaVars() { 03432 if ( $this->mSchemaVars ) { 03433 return $this->mSchemaVars; 03434 } else { 03435 return $this->getDefaultSchemaVars(); 03436 } 03437 } 03438 03447 protected function getDefaultSchemaVars() { 03448 return array(); 03449 } 03450 03458 protected function tableNameCallback( $matches ) { 03459 return $this->tableName( $matches[1] ); 03460 } 03461 03469 protected function indexNameCallback( $matches ) { 03470 return $this->indexName( $matches[1] ); 03471 } 03472 03481 public function lockIsFree( $lockName, $method ) { 03482 return true; 03483 } 03484 03496 public function lock( $lockName, $method, $timeout = 5 ) { 03497 return true; 03498 } 03499 03510 public function unlock( $lockName, $method ) { 03511 return true; 03512 } 03513 03524 public function lockTables( $read, $write, $method, $lowPriority = true ) { 03525 return true; 03526 } 03527 03535 public function unlockTables( $method ) { 03536 return true; 03537 } 03538 03546 public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) { 03547 if( !$this->tableExists( $tableName, $fName ) ) { 03548 return false; 03549 } 03550 $sql = "DROP TABLE " . $this->tableName( $tableName ); 03551 if( $this->cascadingDeletes() ) { 03552 $sql .= " CASCADE"; 03553 } 03554 return $this->query( $sql, $fName ); 03555 } 03556 03563 public function getSearchEngine() { 03564 return 'SearchEngineDummy'; 03565 } 03566 03574 public function getInfinity() { 03575 return 'infinity'; 03576 } 03577 03584 public function encodeExpiry( $expiry ) { 03585 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 03586 ? $this->getInfinity() 03587 : $this->timestamp( $expiry ); 03588 } 03589 03597 public function decodeExpiry( $expiry, $format = TS_MW ) { 03598 return ( $expiry == '' || $expiry == $this->getInfinity() ) 03599 ? 'infinity' 03600 : wfTimestamp( $format, $expiry ); 03601 } 03602 03612 public function setBigSelects( $value = true ) { 03613 // no-op 03614 } 03615 03619 public function __toString() { 03620 return (string)$this->mConn; 03621 } 03622 }