MediaWiki
REL1_22
|
00001 <?php 00034 interface DatabaseType { 00040 function getType(); 00041 00052 function open( $server, $user, $password, $dbName ); 00053 00064 function fetchObject( $res ); 00065 00075 function fetchRow( $res ); 00076 00083 function numRows( $res ); 00084 00092 function numFields( $res ); 00093 00102 function fieldName( $res, $n ); 00103 00116 function insertId(); 00117 00125 function dataSeek( $res, $row ); 00126 00133 function lastErrno(); 00134 00141 function lastError(); 00142 00152 function fieldInfo( $table, $field ); 00153 00161 function indexInfo( $table, $index, $fname = __METHOD__ ); 00162 00169 function affectedRows(); 00170 00177 function strencode( $s ); 00178 00187 function getSoftwareLink(); 00188 00195 function getServerVersion(); 00196 00204 function getServerInfo(); 00205 } 00206 00211 interface IDatabase {} 00212 00217 abstract class DatabaseBase implements IDatabase, DatabaseType { 00219 const DEADLOCK_TRIES = 4; 00221 const DEADLOCK_DELAY_MIN = 500000; 00223 const DEADLOCK_DELAY_MAX = 1500000; 00224 00225 # ------------------------------------------------------------------------------ 00226 # Variables 00227 # ------------------------------------------------------------------------------ 00228 00229 protected $mLastQuery = ''; 00230 protected $mDoneWrites = false; 00231 protected $mPHPError = false; 00232 00233 protected $mServer, $mUser, $mPassword, $mDBname; 00234 00235 protected $mConn = null; 00236 protected $mOpened = false; 00237 00239 protected $mTrxIdleCallbacks = array(); 00241 protected $mTrxPreCommitCallbacks = array(); 00242 00243 protected $mTablePrefix; 00244 protected $mFlags; 00245 protected $mForeign; 00246 protected $mTrxLevel = 0; 00247 protected $mErrorCount = 0; 00248 protected $mLBInfo = array(); 00249 protected $mFakeSlaveLag = null, $mFakeMaster = false; 00250 protected $mDefaultBigSelects = null; 00251 protected $mSchemaVars = false; 00252 00253 protected $preparedArgs; 00254 00255 protected $htmlErrors; 00256 00257 protected $delimiter = ';'; 00258 00266 private $mTrxFname = null; 00267 00274 private $mTrxDoneWrites = false; 00275 00282 private $mTrxAutomatic = false; 00283 00288 protected $fileHandle = null; 00289 00294 protected $allViews = null; 00295 00296 # ------------------------------------------------------------------------------ 00297 # Accessors 00298 # ------------------------------------------------------------------------------ 00299 # These optionally set a variable and return the previous state 00300 00308 public function getServerInfo() { 00309 return $this->getServerVersion(); 00310 } 00311 00315 public function getDelimiter() { 00316 return $this->delimiter; 00317 } 00318 00328 public function debug( $debug = null ) { 00329 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 00330 } 00331 00354 public function bufferResults( $buffer = null ) { 00355 if ( is_null( $buffer ) ) { 00356 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 00357 } else { 00358 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 00359 } 00360 } 00361 00375 public function ignoreErrors( $ignoreErrors = null ) { 00376 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 00377 } 00378 00388 public function trxLevel( $level = null ) { 00389 return wfSetVar( $this->mTrxLevel, $level ); 00390 } 00391 00397 public function errorCount( $count = null ) { 00398 return wfSetVar( $this->mErrorCount, $count ); 00399 } 00400 00406 public function tablePrefix( $prefix = null ) { 00407 return wfSetVar( $this->mTablePrefix, $prefix ); 00408 } 00409 00415 public function setFileHandle( $fh ) { 00416 $this->fileHandle = $fh; 00417 } 00418 00428 public function getLBInfo( $name = null ) { 00429 if ( is_null( $name ) ) { 00430 return $this->mLBInfo; 00431 } else { 00432 if ( array_key_exists( $name, $this->mLBInfo ) ) { 00433 return $this->mLBInfo[$name]; 00434 } else { 00435 return null; 00436 } 00437 } 00438 } 00439 00448 public function setLBInfo( $name, $value = null ) { 00449 if ( is_null( $value ) ) { 00450 $this->mLBInfo = $name; 00451 } else { 00452 $this->mLBInfo[$name] = $value; 00453 } 00454 } 00455 00461 public function setFakeSlaveLag( $lag ) { 00462 $this->mFakeSlaveLag = $lag; 00463 } 00464 00470 public function setFakeMaster( $enabled = true ) { 00471 $this->mFakeMaster = $enabled; 00472 } 00473 00479 public function cascadingDeletes() { 00480 return false; 00481 } 00482 00488 public function cleanupTriggers() { 00489 return false; 00490 } 00491 00498 public function strictIPs() { 00499 return false; 00500 } 00501 00507 public function realTimestamps() { 00508 return false; 00509 } 00510 00516 public function implicitGroupby() { 00517 return true; 00518 } 00519 00526 public function implicitOrderby() { 00527 return true; 00528 } 00529 00536 public function searchableIPs() { 00537 return false; 00538 } 00539 00545 public function functionalIndexes() { 00546 return false; 00547 } 00548 00553 public function lastQuery() { 00554 return $this->mLastQuery; 00555 } 00556 00563 public function doneWrites() { 00564 return $this->mDoneWrites; 00565 } 00566 00573 public function writesOrCallbacksPending() { 00574 return $this->mTrxLevel && ( 00575 $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks 00576 ); 00577 } 00578 00583 public function isOpen() { 00584 return $this->mOpened; 00585 } 00586 00598 public function setFlag( $flag ) { 00599 global $wgDebugDBTransactions; 00600 $this->mFlags |= $flag; 00601 if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) { 00602 wfDebug( "Implicit transactions are now disabled.\n" ); 00603 } 00604 } 00605 00611 public function clearFlag( $flag ) { 00612 global $wgDebugDBTransactions; 00613 $this->mFlags &= ~$flag; 00614 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00615 wfDebug( "Implicit transactions are now disabled.\n" ); 00616 } 00617 } 00618 00625 public function getFlag( $flag ) { 00626 return !!( $this->mFlags & $flag ); 00627 } 00628 00636 public function getProperty( $name ) { 00637 return $this->$name; 00638 } 00639 00643 public function getWikiID() { 00644 if ( $this->mTablePrefix ) { 00645 return "{$this->mDBname}-{$this->mTablePrefix}"; 00646 } else { 00647 return $this->mDBname; 00648 } 00649 } 00650 00656 public function getSchemaPath() { 00657 global $IP; 00658 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) { 00659 return "$IP/maintenance/" . $this->getType() . "/tables.sql"; 00660 } else { 00661 return "$IP/maintenance/tables.sql"; 00662 } 00663 } 00664 00665 # ------------------------------------------------------------------------------ 00666 # Other functions 00667 # ------------------------------------------------------------------------------ 00668 00691 function __construct( $server = false, $user = false, $password = false, $dbName = false, 00692 $flags = 0, $tablePrefix = 'get from global', $foreign = false 00693 ) { 00694 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions; 00695 00696 $this->mFlags = $flags; 00697 00698 if ( $this->mFlags & DBO_DEFAULT ) { 00699 if ( $wgCommandLineMode ) { 00700 $this->mFlags &= ~DBO_TRX; 00701 if ( $wgDebugDBTransactions ) { 00702 wfDebug( "Implicit transaction open disabled.\n" ); 00703 } 00704 } else { 00705 $this->mFlags |= DBO_TRX; 00706 if ( $wgDebugDBTransactions ) { 00707 wfDebug( "Implicit transaction open enabled.\n" ); 00708 } 00709 } 00710 } 00711 00713 if ( $tablePrefix == 'get from global' ) { 00714 $this->mTablePrefix = $wgDBprefix; 00715 } else { 00716 $this->mTablePrefix = $tablePrefix; 00717 } 00718 00719 $this->mForeign = $foreign; 00720 00721 if ( $user ) { 00722 $this->open( $server, $user, $password, $dbName ); 00723 } 00724 } 00725 00731 public function __sleep() { 00732 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' ); 00733 } 00734 00756 final public static function factory( $dbType, $p = array() ) { 00757 $canonicalDBTypes = array( 00758 'mysql' => array( 'mysqli', 'mysql' ), 00759 'postgres' => array(), 00760 'sqlite' => array(), 00761 'oracle' => array(), 00762 'mssql' => array(), 00763 ); 00764 00765 $driver = false; 00766 $dbType = strtolower( $dbType ); 00767 if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) { 00768 $possibleDrivers = $canonicalDBTypes[$dbType]; 00769 if ( !empty( $p['driver'] ) ) { 00770 if ( in_array( $p['driver'], $possibleDrivers ) ) { 00771 $driver = $p['driver']; 00772 } else { 00773 throw new MWException( __METHOD__ . 00774 " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" ); 00775 } 00776 } else { 00777 foreach ( $possibleDrivers as $posDriver ) { 00778 if ( extension_loaded( $posDriver ) ) { 00779 $driver = $posDriver; 00780 break; 00781 } 00782 } 00783 } 00784 } else { 00785 $driver = $dbType; 00786 } 00787 if ( $driver === false ) { 00788 throw new MWException( __METHOD__ . 00789 " no viable database extension found for type '$dbType'" ); 00790 } 00791 00792 $class = 'Database' . ucfirst( $driver ); 00793 if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) { 00794 return new $class( 00795 isset( $p['host'] ) ? $p['host'] : false, 00796 isset( $p['user'] ) ? $p['user'] : false, 00797 isset( $p['password'] ) ? $p['password'] : false, 00798 isset( $p['dbname'] ) ? $p['dbname'] : false, 00799 isset( $p['flags'] ) ? $p['flags'] : 0, 00800 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global', 00801 isset( $p['foreign'] ) ? $p['foreign'] : false 00802 ); 00803 } else { 00804 return null; 00805 } 00806 } 00807 00808 protected function installErrorHandler() { 00809 $this->mPHPError = false; 00810 $this->htmlErrors = ini_set( 'html_errors', '0' ); 00811 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 00812 } 00813 00817 protected function restoreErrorHandler() { 00818 restore_error_handler(); 00819 if ( $this->htmlErrors !== false ) { 00820 ini_set( 'html_errors', $this->htmlErrors ); 00821 } 00822 if ( $this->mPHPError ) { 00823 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 00824 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 00825 return $error; 00826 } else { 00827 return false; 00828 } 00829 } 00830 00836 public function connectionErrorHandler( $errno, $errstr ) { 00837 $this->mPHPError = $errstr; 00838 } 00839 00847 public function close() { 00848 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 00849 throw new MWException( "Transaction idle callbacks still pending." ); 00850 } 00851 $this->mOpened = false; 00852 if ( $this->mConn ) { 00853 if ( $this->trxLevel() ) { 00854 if ( !$this->mTrxAutomatic ) { 00855 wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . 00856 " performing implicit commit before closing connection!" ); 00857 } 00858 00859 $this->commit( __METHOD__, 'flush' ); 00860 } 00861 00862 $ret = $this->closeConnection(); 00863 $this->mConn = false; 00864 return $ret; 00865 } else { 00866 return true; 00867 } 00868 } 00869 00875 abstract protected function closeConnection(); 00876 00881 function reportConnectionError( $error = 'Unknown error' ) { 00882 $myError = $this->lastError(); 00883 if ( $myError ) { 00884 $error = $myError; 00885 } 00886 00887 # New method 00888 throw new DBConnectionError( $this, $error ); 00889 } 00890 00897 abstract protected function doQuery( $sql ); 00898 00907 public function isWriteQuery( $sql ) { 00908 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 00909 } 00910 00933 public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { 00934 global $wgUser, $wgDebugDBTransactions; 00935 00936 $this->mLastQuery = $sql; 00937 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { 00938 # Set a flag indicating that writes have been done 00939 wfDebug( __METHOD__ . ": Writes done: $sql\n" ); 00940 $this->mDoneWrites = true; 00941 } 00942 00943 # Add a comment for easy SHOW PROCESSLIST interpretation 00944 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 00945 $userName = $wgUser->getName(); 00946 if ( mb_strlen( $userName ) > 15 ) { 00947 $userName = mb_substr( $userName, 0, 15 ) . '...'; 00948 } 00949 $userName = str_replace( '/', '', $userName ); 00950 } else { 00951 $userName = ''; 00952 } 00953 00954 // Add trace comment to the begin of the sql string, right after the operator. 00955 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) 00956 $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 ); 00957 00958 # If DBO_TRX is set, start a transaction 00959 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && 00960 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) 00961 { 00962 # Avoid establishing transactions for SHOW and SET statements too - 00963 # that would delay transaction initializations to once connection 00964 # is really used by application 00965 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 00966 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 00967 if ( $wgDebugDBTransactions ) { 00968 wfDebug( "Implicit transaction start.\n" ); 00969 } 00970 $this->begin( __METHOD__ . " ($fname)" ); 00971 $this->mTrxAutomatic = true; 00972 } 00973 } 00974 00975 # Keep track of whether the transaction has write queries pending 00976 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { 00977 $this->mTrxDoneWrites = true; 00978 Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname ); 00979 } 00980 00981 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 00982 if ( !Profiler::instance()->isStub() ) { 00983 # generalizeSQL will probably cut down the query to reasonable 00984 # logging size most of the time. The substr is really just a sanity check. 00985 if ( $isMaster ) { 00986 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00987 $totalProf = 'DatabaseBase::query-master'; 00988 } else { 00989 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00990 $totalProf = 'DatabaseBase::query'; 00991 } 00992 wfProfileIn( $totalProf ); 00993 wfProfileIn( $queryProf ); 00994 } 00995 00996 if ( $this->debug() ) { 00997 static $cnt = 0; 00998 00999 $cnt++; 01000 $sqlx = substr( $commentedSql, 0, 500 ); 01001 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 01002 01003 $master = $isMaster ? 'master' : 'slave'; 01004 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 01005 } 01006 01007 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 01008 01009 # Do the query and handle errors 01010 $ret = $this->doQuery( $commentedSql ); 01011 01012 MWDebug::queryTime( $queryId ); 01013 01014 # Try reconnecting if the connection was lost 01015 if ( false === $ret && $this->wasErrorReissuable() ) { 01016 # Transaction is gone, like it or not 01017 $this->mTrxLevel = 0; 01018 $this->mTrxIdleCallbacks = array(); // cancel 01019 $this->mTrxPreCommitCallbacks = array(); // cancel 01020 wfDebug( "Connection lost, reconnecting...\n" ); 01021 01022 if ( $this->ping() ) { 01023 wfDebug( "Reconnected\n" ); 01024 $sqlx = substr( $commentedSql, 0, 500 ); 01025 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 01026 global $wgRequestTime; 01027 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 01028 if ( $elapsed < 300 ) { 01029 # Not a database error to lose a transaction after a minute or two 01030 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); 01031 } 01032 $ret = $this->doQuery( $commentedSql ); 01033 } else { 01034 wfDebug( "Failed\n" ); 01035 } 01036 } 01037 01038 if ( false === $ret ) { 01039 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 01040 } 01041 01042 if ( !Profiler::instance()->isStub() ) { 01043 wfProfileOut( $queryProf ); 01044 wfProfileOut( $totalProf ); 01045 } 01046 01047 return $this->resultObject( $ret ); 01048 } 01049 01061 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 01062 # Ignore errors during error handling to avoid infinite recursion 01063 $ignore = $this->ignoreErrors( true ); 01064 ++$this->mErrorCount; 01065 01066 if ( $ignore || $tempIgnore ) { 01067 wfDebug( "SQL ERROR (ignored): $error\n" ); 01068 $this->ignoreErrors( $ignore ); 01069 } else { 01070 $sql1line = str_replace( "\n", "\\n", $sql ); 01071 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" ); 01072 wfDebug( "SQL ERROR: " . $error . "\n" ); 01073 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 01074 } 01075 } 01076 01091 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 01092 /* MySQL doesn't support prepared statements (yet), so just 01093 pack up the query for reference. We'll manually replace 01094 the bits later. */ 01095 return array( 'query' => $sql, 'func' => $func ); 01096 } 01097 01102 protected function freePrepared( $prepared ) { 01103 /* No-op by default */ 01104 } 01105 01113 public function execute( $prepared, $args = null ) { 01114 if ( !is_array( $args ) ) { 01115 # Pull the var args 01116 $args = func_get_args(); 01117 array_shift( $args ); 01118 } 01119 01120 $sql = $this->fillPrepared( $prepared['query'], $args ); 01121 01122 return $this->query( $sql, $prepared['func'] ); 01123 } 01124 01132 public function fillPrepared( $preparedQuery, $args ) { 01133 reset( $args ); 01134 $this->preparedArgs =& $args; 01135 01136 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 01137 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 01138 } 01139 01149 protected function fillPreparedArg( $matches ) { 01150 switch ( $matches[1] ) { 01151 case '\\?': 01152 return '?'; 01153 case '\\!': 01154 return '!'; 01155 case '\\&': 01156 return '&'; 01157 } 01158 01159 list( /* $n */, $arg ) = each( $this->preparedArgs ); 01160 01161 switch ( $matches[1] ) { 01162 case '?': 01163 return $this->addQuotes( $arg ); 01164 case '!': 01165 return $arg; 01166 case '&': 01167 # return $this->addQuotes( file_get_contents( $arg ) ); 01168 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); 01169 default: 01170 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); 01171 } 01172 } 01173 01181 public function freeResult( $res ) { 01182 } 01183 01201 public function selectField( $table, $var, $cond = '', $fname = __METHOD__, 01202 $options = array() 01203 ) { 01204 if ( !is_array( $options ) ) { 01205 $options = array( $options ); 01206 } 01207 01208 $options['LIMIT'] = 1; 01209 01210 $res = $this->select( $table, $var, $cond, $fname, $options ); 01211 01212 if ( $res === false || !$this->numRows( $res ) ) { 01213 return false; 01214 } 01215 01216 $row = $this->fetchRow( $res ); 01217 01218 if ( $row !== false ) { 01219 return reset( $row ); 01220 } else { 01221 return false; 01222 } 01223 } 01224 01234 public function makeSelectOptions( $options ) { 01235 $preLimitTail = $postLimitTail = ''; 01236 $startOpts = ''; 01237 01238 $noKeyOptions = array(); 01239 01240 foreach ( $options as $key => $option ) { 01241 if ( is_numeric( $key ) ) { 01242 $noKeyOptions[$option] = true; 01243 } 01244 } 01245 01246 $preLimitTail .= $this->makeGroupByWithHaving( $options ); 01247 01248 $preLimitTail .= $this->makeOrderBy( $options ); 01249 01250 // if (isset($options['LIMIT'])) { 01251 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 01252 // isset($options['OFFSET']) ? $options['OFFSET'] 01253 // : false); 01254 // } 01255 01256 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 01257 $postLimitTail .= ' FOR UPDATE'; 01258 } 01259 01260 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 01261 $postLimitTail .= ' LOCK IN SHARE MODE'; 01262 } 01263 01264 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01265 $startOpts .= 'DISTINCT'; 01266 } 01267 01268 # Various MySQL extensions 01269 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 01270 $startOpts .= ' /*! STRAIGHT_JOIN */'; 01271 } 01272 01273 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 01274 $startOpts .= ' HIGH_PRIORITY'; 01275 } 01276 01277 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 01278 $startOpts .= ' SQL_BIG_RESULT'; 01279 } 01280 01281 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 01282 $startOpts .= ' SQL_BUFFER_RESULT'; 01283 } 01284 01285 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 01286 $startOpts .= ' SQL_SMALL_RESULT'; 01287 } 01288 01289 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 01290 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 01291 } 01292 01293 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 01294 $startOpts .= ' SQL_CACHE'; 01295 } 01296 01297 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 01298 $startOpts .= ' SQL_NO_CACHE'; 01299 } 01300 01301 if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) { 01302 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01303 } else { 01304 $useIndex = ''; 01305 } 01306 01307 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01308 } 01309 01318 public function makeGroupByWithHaving( $options ) { 01319 $sql = ''; 01320 if ( isset( $options['GROUP BY'] ) ) { 01321 $gb = is_array( $options['GROUP BY'] ) 01322 ? implode( ',', $options['GROUP BY'] ) 01323 : $options['GROUP BY']; 01324 $sql .= ' GROUP BY ' . $gb; 01325 } 01326 if ( isset( $options['HAVING'] ) ) { 01327 $having = is_array( $options['HAVING'] ) 01328 ? $this->makeList( $options['HAVING'], LIST_AND ) 01329 : $options['HAVING']; 01330 $sql .= ' HAVING ' . $having; 01331 } 01332 return $sql; 01333 } 01334 01343 public function makeOrderBy( $options ) { 01344 if ( isset( $options['ORDER BY'] ) ) { 01345 $ob = is_array( $options['ORDER BY'] ) 01346 ? implode( ',', $options['ORDER BY'] ) 01347 : $options['ORDER BY']; 01348 return ' ORDER BY ' . $ob; 01349 } 01350 return ''; 01351 } 01352 01492 public function select( $table, $vars, $conds = '', $fname = __METHOD__, 01493 $options = array(), $join_conds = array() ) { 01494 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 01495 01496 return $this->query( $sql, $fname ); 01497 } 01498 01515 public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, 01516 $options = array(), $join_conds = array() ) 01517 { 01518 if ( is_array( $vars ) ) { 01519 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 01520 } 01521 01522 $options = (array)$options; 01523 $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 01524 ? $options['USE INDEX'] 01525 : array(); 01526 01527 if ( is_array( $table ) ) { 01528 $from = ' FROM ' . 01529 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds ); 01530 } elseif ( $table != '' ) { 01531 if ( $table[0] == ' ' ) { 01532 $from = ' FROM ' . $table; 01533 } else { 01534 $from = ' FROM ' . 01535 $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() ); 01536 } 01537 } else { 01538 $from = ''; 01539 } 01540 01541 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = 01542 $this->makeSelectOptions( $options ); 01543 01544 if ( !empty( $conds ) ) { 01545 if ( is_array( $conds ) ) { 01546 $conds = $this->makeList( $conds, LIST_AND ); 01547 } 01548 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 01549 } else { 01550 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 01551 } 01552 01553 if ( isset( $options['LIMIT'] ) ) { 01554 $sql = $this->limitResult( $sql, $options['LIMIT'], 01555 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 01556 } 01557 $sql = "$sql $postLimitTail"; 01558 01559 if ( isset( $options['EXPLAIN'] ) ) { 01560 $sql = 'EXPLAIN ' . $sql; 01561 } 01562 01563 return $sql; 01564 } 01565 01580 public function selectRow( $table, $vars, $conds, $fname = __METHOD__, 01581 $options = array(), $join_conds = array() ) 01582 { 01583 $options = (array)$options; 01584 $options['LIMIT'] = 1; 01585 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 01586 01587 if ( $res === false ) { 01588 return false; 01589 } 01590 01591 if ( !$this->numRows( $res ) ) { 01592 return false; 01593 } 01594 01595 $obj = $this->fetchObject( $res ); 01596 01597 return $obj; 01598 } 01599 01620 public function estimateRowCount( $table, $vars = '*', $conds = '', 01621 $fname = __METHOD__, $options = array() ) 01622 { 01623 $rows = 0; 01624 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 01625 01626 if ( $res ) { 01627 $row = $this->fetchRow( $res ); 01628 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01629 } 01630 01631 return $rows; 01632 } 01633 01642 static function generalizeSQL( $sql ) { 01643 # This does the same as the regexp below would do, but in such a way 01644 # as to avoid crashing php on some large strings. 01645 # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql ); 01646 01647 $sql = str_replace( "\\\\", '', $sql ); 01648 $sql = str_replace( "\\'", '', $sql ); 01649 $sql = str_replace( "\\\"", '', $sql ); 01650 $sql = preg_replace( "/'.*'/s", "'X'", $sql ); 01651 $sql = preg_replace( '/".*"/s', "'X'", $sql ); 01652 01653 # All newlines, tabs, etc replaced by single space 01654 $sql = preg_replace( '/\s+/', ' ', $sql ); 01655 01656 # All numbers => N 01657 $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql ); 01658 $sql = preg_replace( '/-?\d+/s', 'N', $sql ); 01659 01660 return $sql; 01661 } 01662 01671 public function fieldExists( $table, $field, $fname = __METHOD__ ) { 01672 $info = $this->fieldInfo( $table, $field ); 01673 01674 return (bool)$info; 01675 } 01676 01688 public function indexExists( $table, $index, $fname = __METHOD__ ) { 01689 if ( !$this->tableExists( $table ) ) { 01690 return null; 01691 } 01692 01693 $info = $this->indexInfo( $table, $index, $fname ); 01694 if ( is_null( $info ) ) { 01695 return null; 01696 } else { 01697 return $info !== false; 01698 } 01699 } 01700 01709 public function tableExists( $table, $fname = __METHOD__ ) { 01710 $table = $this->tableName( $table ); 01711 $old = $this->ignoreErrors( true ); 01712 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 01713 $this->ignoreErrors( $old ); 01714 01715 return (bool)$res; 01716 } 01717 01724 public function fieldType( $res, $index ) { 01725 if ( $res instanceof ResultWrapper ) { 01726 $res = $res->result; 01727 } 01728 01729 return mysql_field_type( $res, $index ); 01730 } 01731 01740 public function indexUnique( $table, $index ) { 01741 $indexInfo = $this->indexInfo( $table, $index ); 01742 01743 if ( !$indexInfo ) { 01744 return null; 01745 } 01746 01747 return !$indexInfo[0]->Non_unique; 01748 } 01749 01756 protected function makeInsertOptions( $options ) { 01757 return implode( ' ', $options ); 01758 } 01759 01793 public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { 01794 # No rows to insert, easy just return now 01795 if ( !count( $a ) ) { 01796 return true; 01797 } 01798 01799 $table = $this->tableName( $table ); 01800 01801 if ( !is_array( $options ) ) { 01802 $options = array( $options ); 01803 } 01804 01805 $fh = null; 01806 if ( isset( $options['fileHandle'] ) ) { 01807 $fh = $options['fileHandle']; 01808 } 01809 $options = $this->makeInsertOptions( $options ); 01810 01811 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 01812 $multi = true; 01813 $keys = array_keys( $a[0] ); 01814 } else { 01815 $multi = false; 01816 $keys = array_keys( $a ); 01817 } 01818 01819 $sql = 'INSERT ' . $options . 01820 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 01821 01822 if ( $multi ) { 01823 $first = true; 01824 foreach ( $a as $row ) { 01825 if ( $first ) { 01826 $first = false; 01827 } else { 01828 $sql .= ','; 01829 } 01830 $sql .= '(' . $this->makeList( $row ) . ')'; 01831 } 01832 } else { 01833 $sql .= '(' . $this->makeList( $a ) . ')'; 01834 } 01835 01836 if ( $fh !== null && false === fwrite( $fh, $sql ) ) { 01837 return false; 01838 } elseif ( $fh !== null ) { 01839 return true; 01840 } 01841 01842 return (bool)$this->query( $sql, $fname ); 01843 } 01844 01851 protected function makeUpdateOptions( $options ) { 01852 if ( !is_array( $options ) ) { 01853 $options = array( $options ); 01854 } 01855 01856 $opts = array(); 01857 01858 if ( in_array( 'LOW_PRIORITY', $options ) ) { 01859 $opts[] = $this->lowPriorityOption(); 01860 } 01861 01862 if ( in_array( 'IGNORE', $options ) ) { 01863 $opts[] = 'IGNORE'; 01864 } 01865 01866 return implode( ' ', $opts ); 01867 } 01868 01892 function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) { 01893 $table = $this->tableName( $table ); 01894 $opts = $this->makeUpdateOptions( $options ); 01895 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 01896 01897 if ( $conds !== array() && $conds !== '*' ) { 01898 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 01899 } 01900 01901 return $this->query( $sql, $fname ); 01902 } 01903 01918 public function makeList( $a, $mode = LIST_COMMA ) { 01919 if ( !is_array( $a ) ) { 01920 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 01921 } 01922 01923 $first = true; 01924 $list = ''; 01925 01926 foreach ( $a as $field => $value ) { 01927 if ( !$first ) { 01928 if ( $mode == LIST_AND ) { 01929 $list .= ' AND '; 01930 } elseif ( $mode == LIST_OR ) { 01931 $list .= ' OR '; 01932 } else { 01933 $list .= ','; 01934 } 01935 } else { 01936 $first = false; 01937 } 01938 01939 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 01940 $list .= "($value)"; 01941 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 01942 $list .= "$value"; 01943 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 01944 if ( count( $value ) == 0 ) { 01945 throw new MWException( __METHOD__ . ": empty input for field $field" ); 01946 } elseif ( count( $value ) == 1 ) { 01947 // Special-case single values, as IN isn't terribly efficient 01948 // Don't necessarily assume the single key is 0; we don't 01949 // enforce linear numeric ordering on other arrays here. 01950 $value = array_values( $value ); 01951 $list .= $field . " = " . $this->addQuotes( $value[0] ); 01952 } else { 01953 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 01954 } 01955 } elseif ( $value === null ) { 01956 if ( $mode == LIST_AND || $mode == LIST_OR ) { 01957 $list .= "$field IS "; 01958 } elseif ( $mode == LIST_SET ) { 01959 $list .= "$field = "; 01960 } 01961 $list .= 'NULL'; 01962 } else { 01963 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 01964 $list .= "$field = "; 01965 } 01966 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 01967 } 01968 } 01969 01970 return $list; 01971 } 01972 01983 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 01984 $conds = array(); 01985 01986 foreach ( $data as $base => $sub ) { 01987 if ( count( $sub ) ) { 01988 $conds[] = $this->makeList( 01989 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 01990 LIST_AND ); 01991 } 01992 } 01993 01994 if ( $conds ) { 01995 return $this->makeList( $conds, LIST_OR ); 01996 } else { 01997 // Nothing to search for... 01998 return false; 01999 } 02000 } 02001 02010 public function aggregateValue( $valuedata, $valuename = 'value' ) { 02011 return $valuename; 02012 } 02013 02018 public function bitNot( $field ) { 02019 return "(~$field)"; 02020 } 02021 02027 public function bitAnd( $fieldLeft, $fieldRight ) { 02028 return "($fieldLeft & $fieldRight)"; 02029 } 02030 02036 public function bitOr( $fieldLeft, $fieldRight ) { 02037 return "($fieldLeft | $fieldRight)"; 02038 } 02039 02045 public function buildConcat( $stringList ) { 02046 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 02047 } 02048 02058 public function selectDB( $db ) { 02059 # Stub. Shouldn't cause serious problems if it's not overridden, but 02060 # if your database engine supports a concept similar to MySQL's 02061 # databases you may as well. 02062 $this->mDBname = $db; 02063 return true; 02064 } 02065 02069 public function getDBname() { 02070 return $this->mDBname; 02071 } 02072 02076 public function getServer() { 02077 return $this->mServer; 02078 } 02079 02097 public function tableName( $name, $format = 'quoted' ) { 02098 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables; 02099 # Skip the entire process when we have a string quoted on both ends. 02100 # Note that we check the end so that we will still quote any use of 02101 # use of `database`.table. But won't break things if someone wants 02102 # to query a database table with a dot in the name. 02103 if ( $this->isQuotedIdentifier( $name ) ) { 02104 return $name; 02105 } 02106 02107 # Lets test for any bits of text that should never show up in a table 02108 # name. Basically anything like JOIN or ON which are actually part of 02109 # SQL queries, but may end up inside of the table value to combine 02110 # sql. Such as how the API is doing. 02111 # Note that we use a whitespace test rather than a \b test to avoid 02112 # any remote case where a word like on may be inside of a table name 02113 # surrounded by symbols which may be considered word breaks. 02114 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 02115 return $name; 02116 } 02117 02118 # Split database and table into proper variables. 02119 # We reverse the explode so that database.table and table both output 02120 # the correct table. 02121 $dbDetails = explode( '.', $name, 2 ); 02122 if ( count( $dbDetails ) == 2 ) { 02123 list( $database, $table ) = $dbDetails; 02124 # We don't want any prefix added in this case 02125 $prefix = ''; 02126 } else { 02127 list( $table ) = $dbDetails; 02128 if ( $wgSharedDB !== null # We have a shared database 02129 && $this->mForeign == false # We're not working on a foreign database 02130 && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' 02131 && in_array( $table, $wgSharedTables ) # A shared table is selected 02132 ) { 02133 $database = $wgSharedDB; 02134 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; 02135 } else { 02136 $database = null; 02137 $prefix = $this->mTablePrefix; # Default prefix 02138 } 02139 } 02140 02141 # Quote $table and apply the prefix if not quoted. 02142 $tableName = "{$prefix}{$table}"; 02143 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) ) { 02144 $tableName = $this->addIdentifierQuotes( $tableName ); 02145 } 02146 02147 # Quote $database and merge it with the table name if needed 02148 if ( $database !== null ) { 02149 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 02150 $database = $this->addIdentifierQuotes( $database ); 02151 } 02152 $tableName = $database . '.' . $tableName; 02153 } 02154 02155 return $tableName; 02156 } 02157 02169 public function tableNames() { 02170 $inArray = func_get_args(); 02171 $retVal = array(); 02172 02173 foreach ( $inArray as $name ) { 02174 $retVal[$name] = $this->tableName( $name ); 02175 } 02176 02177 return $retVal; 02178 } 02179 02191 public function tableNamesN() { 02192 $inArray = func_get_args(); 02193 $retVal = array(); 02194 02195 foreach ( $inArray as $name ) { 02196 $retVal[] = $this->tableName( $name ); 02197 } 02198 02199 return $retVal; 02200 } 02201 02210 public function tableNameWithAlias( $name, $alias = false ) { 02211 if ( !$alias || $alias == $name ) { 02212 return $this->tableName( $name ); 02213 } else { 02214 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 02215 } 02216 } 02217 02224 public function tableNamesWithAlias( $tables ) { 02225 $retval = array(); 02226 foreach ( $tables as $alias => $table ) { 02227 if ( is_numeric( $alias ) ) { 02228 $alias = $table; 02229 } 02230 $retval[] = $this->tableNameWithAlias( $table, $alias ); 02231 } 02232 return $retval; 02233 } 02234 02243 public function fieldNameWithAlias( $name, $alias = false ) { 02244 if ( !$alias || (string)$alias === (string)$name ) { 02245 return $name; 02246 } else { 02247 return $name . ' AS ' . $alias; //PostgreSQL needs AS 02248 } 02249 } 02250 02257 public function fieldNamesWithAlias( $fields ) { 02258 $retval = array(); 02259 foreach ( $fields as $alias => $field ) { 02260 if ( is_numeric( $alias ) ) { 02261 $alias = $field; 02262 } 02263 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 02264 } 02265 return $retval; 02266 } 02267 02277 protected function tableNamesWithUseIndexOrJOIN( 02278 $tables, $use_index = array(), $join_conds = array() 02279 ) { 02280 $ret = array(); 02281 $retJOIN = array(); 02282 $use_index = (array)$use_index; 02283 $join_conds = (array)$join_conds; 02284 02285 foreach ( $tables as $alias => $table ) { 02286 if ( !is_string( $alias ) ) { 02287 // No alias? Set it equal to the table name 02288 $alias = $table; 02289 } 02290 // Is there a JOIN clause for this table? 02291 if ( isset( $join_conds[$alias] ) ) { 02292 list( $joinType, $conds ) = $join_conds[$alias]; 02293 $tableClause = $joinType; 02294 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 02295 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 02296 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 02297 if ( $use != '' ) { 02298 $tableClause .= ' ' . $use; 02299 } 02300 } 02301 $on = $this->makeList( (array)$conds, LIST_AND ); 02302 if ( $on != '' ) { 02303 $tableClause .= ' ON (' . $on . ')'; 02304 } 02305 02306 $retJOIN[] = $tableClause; 02307 // Is there an INDEX clause for this table? 02308 } elseif ( isset( $use_index[$alias] ) ) { 02309 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02310 $tableClause .= ' ' . $this->useIndexClause( 02311 implode( ',', (array)$use_index[$alias] ) ); 02312 02313 $ret[] = $tableClause; 02314 } else { 02315 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02316 02317 $ret[] = $tableClause; 02318 } 02319 } 02320 02321 // We can't separate explicit JOIN clauses with ',', use ' ' for those 02322 $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 02323 $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 02324 02325 // Compile our final table clause 02326 return implode( ' ', array( $implicitJoins, $explicitJoins ) ); 02327 } 02328 02336 protected function indexName( $index ) { 02337 // Backwards-compatibility hack 02338 $renamed = array( 02339 'ar_usertext_timestamp' => 'usertext_timestamp', 02340 'un_user_id' => 'user_id', 02341 'un_user_ip' => 'user_ip', 02342 ); 02343 02344 if ( isset( $renamed[$index] ) ) { 02345 return $renamed[$index]; 02346 } else { 02347 return $index; 02348 } 02349 } 02350 02358 public function addQuotes( $s ) { 02359 if ( $s === null ) { 02360 return 'NULL'; 02361 } else { 02362 # This will also quote numeric values. This should be harmless, 02363 # and protects against weird problems that occur when they really 02364 # _are_ strings such as article titles and string->number->string 02365 # conversion is not 1:1. 02366 return "'" . $this->strencode( $s ) . "'"; 02367 } 02368 } 02369 02380 public function addIdentifierQuotes( $s ) { 02381 return '"' . str_replace( '"', '""', $s ) . '"'; 02382 } 02383 02392 public function isQuotedIdentifier( $name ) { 02393 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 02394 } 02395 02400 protected function escapeLikeInternal( $s ) { 02401 $s = str_replace( '\\', '\\\\', $s ); 02402 $s = $this->strencode( $s ); 02403 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s ); 02404 02405 return $s; 02406 } 02407 02420 public function buildLike() { 02421 $params = func_get_args(); 02422 02423 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 02424 $params = $params[0]; 02425 } 02426 02427 $s = ''; 02428 02429 foreach ( $params as $value ) { 02430 if ( $value instanceof LikeMatch ) { 02431 $s .= $value->toString(); 02432 } else { 02433 $s .= $this->escapeLikeInternal( $value ); 02434 } 02435 } 02436 02437 return " LIKE '" . $s . "' "; 02438 } 02439 02445 public function anyChar() { 02446 return new LikeMatch( '_' ); 02447 } 02448 02454 public function anyString() { 02455 return new LikeMatch( '%' ); 02456 } 02457 02469 public function nextSequenceValue( $seqName ) { 02470 return null; 02471 } 02472 02483 public function useIndexClause( $index ) { 02484 return ''; 02485 } 02486 02509 public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { 02510 $quotedTable = $this->tableName( $table ); 02511 02512 if ( count( $rows ) == 0 ) { 02513 return; 02514 } 02515 02516 # Single row case 02517 if ( !is_array( reset( $rows ) ) ) { 02518 $rows = array( $rows ); 02519 } 02520 02521 foreach ( $rows as $row ) { 02522 # Delete rows which collide 02523 if ( $uniqueIndexes ) { 02524 $sql = "DELETE FROM $quotedTable WHERE "; 02525 $first = true; 02526 foreach ( $uniqueIndexes as $index ) { 02527 if ( $first ) { 02528 $first = false; 02529 $sql .= '( '; 02530 } else { 02531 $sql .= ' ) OR ( '; 02532 } 02533 if ( is_array( $index ) ) { 02534 $first2 = true; 02535 foreach ( $index as $col ) { 02536 if ( $first2 ) { 02537 $first2 = false; 02538 } else { 02539 $sql .= ' AND '; 02540 } 02541 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 02542 } 02543 } else { 02544 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 02545 } 02546 } 02547 $sql .= ' )'; 02548 $this->query( $sql, $fname ); 02549 } 02550 02551 # Now insert the row 02552 $this->insert( $table, $row, $fname ); 02553 } 02554 } 02555 02566 protected function nativeReplace( $table, $rows, $fname ) { 02567 $table = $this->tableName( $table ); 02568 02569 # Single row case 02570 if ( !is_array( reset( $rows ) ) ) { 02571 $rows = array( $rows ); 02572 } 02573 02574 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 02575 $first = true; 02576 02577 foreach ( $rows as $row ) { 02578 if ( $first ) { 02579 $first = false; 02580 } else { 02581 $sql .= ','; 02582 } 02583 02584 $sql .= '(' . $this->makeList( $row ) . ')'; 02585 } 02586 02587 return $this->query( $sql, $fname ); 02588 } 02589 02625 public function upsert( 02626 $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__ 02627 ) { 02628 if ( !count( $rows ) ) { 02629 return true; // nothing to do 02630 } 02631 $rows = is_array( reset( $rows ) ) ? $rows : array( $rows ); 02632 02633 if ( count( $uniqueIndexes ) ) { 02634 $clauses = array(); // list WHERE clauses that each identify a single row 02635 foreach ( $rows as $row ) { 02636 foreach ( $uniqueIndexes as $index ) { 02637 $index = is_array( $index ) ? $index : array( $index ); // columns 02638 $rowKey = array(); // unique key to this row 02639 foreach ( $index as $column ) { 02640 $rowKey[$column] = $row[$column]; 02641 } 02642 $clauses[] = $this->makeList( $rowKey, LIST_AND ); 02643 } 02644 } 02645 $where = array( $this->makeList( $clauses, LIST_OR ) ); 02646 } else { 02647 $where = false; 02648 } 02649 02650 $useTrx = !$this->mTrxLevel; 02651 if ( $useTrx ) { 02652 $this->begin( $fname ); 02653 } 02654 try { 02655 # Update any existing conflicting row(s) 02656 if ( $where !== false ) { 02657 $ok = $this->update( $table, $set, $where, $fname ); 02658 } else { 02659 $ok = true; 02660 } 02661 # Now insert any non-conflicting row(s) 02662 $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok; 02663 } catch ( Exception $e ) { 02664 if ( $useTrx ) { 02665 $this->rollback( $fname ); 02666 } 02667 throw $e; 02668 } 02669 if ( $useTrx ) { 02670 $this->commit( $fname ); 02671 } 02672 02673 return $ok; 02674 } 02675 02697 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 02698 $fname = __METHOD__ ) 02699 { 02700 if ( !$conds ) { 02701 throw new DBUnexpectedError( $this, 02702 'DatabaseBase::deleteJoin() called with empty $conds' ); 02703 } 02704 02705 $delTable = $this->tableName( $delTable ); 02706 $joinTable = $this->tableName( $joinTable ); 02707 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 02708 if ( $conds != '*' ) { 02709 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 02710 } 02711 $sql .= ')'; 02712 02713 $this->query( $sql, $fname ); 02714 } 02715 02724 public function textFieldSize( $table, $field ) { 02725 $table = $this->tableName( $table ); 02726 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 02727 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 02728 $row = $this->fetchObject( $res ); 02729 02730 $m = array(); 02731 02732 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 02733 $size = $m[1]; 02734 } else { 02735 $size = -1; 02736 } 02737 02738 return $size; 02739 } 02740 02749 public function lowPriorityOption() { 02750 return ''; 02751 } 02752 02764 public function delete( $table, $conds, $fname = __METHOD__ ) { 02765 if ( !$conds ) { 02766 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 02767 } 02768 02769 $table = $this->tableName( $table ); 02770 $sql = "DELETE FROM $table"; 02771 02772 if ( $conds != '*' ) { 02773 if ( is_array( $conds ) ) { 02774 $conds = $this->makeList( $conds, LIST_AND ); 02775 } 02776 $sql .= ' WHERE ' . $conds; 02777 } 02778 02779 return $this->query( $sql, $fname ); 02780 } 02781 02808 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 02809 $fname = __METHOD__, 02810 $insertOptions = array(), $selectOptions = array() ) 02811 { 02812 $destTable = $this->tableName( $destTable ); 02813 02814 if ( is_array( $insertOptions ) ) { 02815 $insertOptions = implode( ' ', $insertOptions ); 02816 } 02817 02818 if ( !is_array( $selectOptions ) ) { 02819 $selectOptions = array( $selectOptions ); 02820 } 02821 02822 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 02823 02824 if ( is_array( $srcTable ) ) { 02825 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 02826 } else { 02827 $srcTable = $this->tableName( $srcTable ); 02828 } 02829 02830 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 02831 " SELECT $startOpts " . implode( ',', $varMap ) . 02832 " FROM $srcTable $useIndex "; 02833 02834 if ( $conds != '*' ) { 02835 if ( is_array( $conds ) ) { 02836 $conds = $this->makeList( $conds, LIST_AND ); 02837 } 02838 $sql .= " WHERE $conds"; 02839 } 02840 02841 $sql .= " $tailOpts"; 02842 02843 return $this->query( $sql, $fname ); 02844 } 02845 02866 public function limitResult( $sql, $limit, $offset = false ) { 02867 if ( !is_numeric( $limit ) ) { 02868 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 02869 } 02870 return "$sql LIMIT " 02871 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 02872 . "{$limit} "; 02873 } 02874 02880 public function unionSupportsOrderAndLimit() { 02881 return true; // True for almost every DB supported 02882 } 02883 02892 public function unionQueries( $sqls, $all ) { 02893 $glue = $all ? ') UNION ALL (' : ') UNION ('; 02894 return '(' . implode( $glue, $sqls ) . ')'; 02895 } 02896 02906 public function conditional( $cond, $trueVal, $falseVal ) { 02907 if ( is_array( $cond ) ) { 02908 $cond = $this->makeList( $cond, LIST_AND ); 02909 } 02910 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 02911 } 02912 02923 public function strreplace( $orig, $old, $new ) { 02924 return "REPLACE({$orig}, {$old}, {$new})"; 02925 } 02926 02933 public function getServerUptime() { 02934 return 0; 02935 } 02936 02943 public function wasDeadlock() { 02944 return false; 02945 } 02946 02953 public function wasLockTimeout() { 02954 return false; 02955 } 02956 02964 public function wasErrorReissuable() { 02965 return false; 02966 } 02967 02974 public function wasReadOnlyError() { 02975 return false; 02976 } 02977 02996 public function deadlockLoop() { 02997 $this->begin( __METHOD__ ); 02998 $args = func_get_args(); 02999 $function = array_shift( $args ); 03000 $oldIgnore = $this->ignoreErrors( true ); 03001 $tries = self::DEADLOCK_TRIES; 03002 03003 if ( is_array( $function ) ) { 03004 $fname = $function[0]; 03005 } else { 03006 $fname = $function; 03007 } 03008 03009 do { 03010 $retVal = call_user_func_array( $function, $args ); 03011 $error = $this->lastError(); 03012 $errno = $this->lastErrno(); 03013 $sql = $this->lastQuery(); 03014 03015 if ( $errno ) { 03016 if ( $this->wasDeadlock() ) { 03017 # Retry 03018 usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) ); 03019 } else { 03020 $this->reportQueryError( $error, $errno, $sql, $fname ); 03021 } 03022 } 03023 } while ( $this->wasDeadlock() && --$tries > 0 ); 03024 03025 $this->ignoreErrors( $oldIgnore ); 03026 03027 if ( $tries <= 0 ) { 03028 $this->rollback( __METHOD__ ); 03029 $this->reportQueryError( $error, $errno, $sql, $fname ); 03030 return false; 03031 } else { 03032 $this->commit( __METHOD__ ); 03033 return $retVal; 03034 } 03035 } 03036 03048 public function masterPosWait( DBMasterPos $pos, $timeout ) { 03049 wfProfileIn( __METHOD__ ); 03050 03051 if ( !is_null( $this->mFakeSlaveLag ) ) { 03052 $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 ); 03053 03054 if ( $wait > $timeout * 1e6 ) { 03055 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" ); 03056 wfProfileOut( __METHOD__ ); 03057 return -1; 03058 } elseif ( $wait > 0 ) { 03059 wfDebug( "Fake slave waiting $wait us\n" ); 03060 usleep( $wait ); 03061 wfProfileOut( __METHOD__ ); 03062 return 1; 03063 } else { 03064 wfDebug( "Fake slave up to date ($wait us)\n" ); 03065 wfProfileOut( __METHOD__ ); 03066 return 0; 03067 } 03068 } 03069 03070 wfProfileOut( __METHOD__ ); 03071 03072 # Real waits are implemented in the subclass. 03073 return 0; 03074 } 03075 03081 public function getSlavePos() { 03082 if ( !is_null( $this->mFakeSlaveLag ) ) { 03083 $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag ); 03084 wfDebug( __METHOD__ . ": fake slave pos = $pos\n" ); 03085 return $pos; 03086 } else { 03087 # Stub 03088 return false; 03089 } 03090 } 03091 03097 public function getMasterPos() { 03098 if ( $this->mFakeMaster ) { 03099 return new MySQLMasterPos( 'fake', microtime( true ) ); 03100 } else { 03101 return false; 03102 } 03103 } 03104 03119 final public function onTransactionIdle( $callback ) { 03120 $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() ); 03121 if ( !$this->mTrxLevel ) { 03122 $this->runOnTransactionIdleCallbacks(); 03123 } 03124 } 03125 03137 final public function onTransactionPreCommitOrIdle( $callback ) { 03138 if ( $this->mTrxLevel ) { 03139 $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() ); 03140 } else { 03141 $this->onTransactionIdle( $callback ); // this will trigger immediately 03142 } 03143 } 03144 03150 protected function runOnTransactionIdleCallbacks() { 03151 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? 03152 03153 $e = null; // last exception 03154 do { // callbacks may add callbacks :) 03155 $callbacks = $this->mTrxIdleCallbacks; 03156 $this->mTrxIdleCallbacks = array(); // recursion guard 03157 foreach ( $callbacks as $callback ) { 03158 try { 03159 list( $phpCallback ) = $callback; 03160 $this->clearFlag( DBO_TRX ); // make each query its own transaction 03161 call_user_func( $phpCallback ); 03162 $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() 03163 } catch ( Exception $e ) {} 03164 } 03165 } while ( count( $this->mTrxIdleCallbacks ) ); 03166 03167 if ( $e instanceof Exception ) { 03168 throw $e; // re-throw any last exception 03169 } 03170 } 03171 03177 protected function runOnTransactionPreCommitCallbacks() { 03178 $e = null; // last exception 03179 do { // callbacks may add callbacks :) 03180 $callbacks = $this->mTrxPreCommitCallbacks; 03181 $this->mTrxPreCommitCallbacks = array(); // recursion guard 03182 foreach ( $callbacks as $callback ) { 03183 try { 03184 list( $phpCallback ) = $callback; 03185 call_user_func( $phpCallback ); 03186 } catch ( Exception $e ) {} 03187 } 03188 } while ( count( $this->mTrxPreCommitCallbacks ) ); 03189 03190 if ( $e instanceof Exception ) { 03191 throw $e; // re-throw any last exception 03192 } 03193 } 03194 03207 final public function begin( $fname = __METHOD__ ) { 03208 global $wgDebugDBTransactions; 03209 03210 if ( $this->mTrxLevel ) { // implicit commit 03211 if ( !$this->mTrxAutomatic ) { 03212 // We want to warn about inadvertently nested begin/commit pairs, but not about 03213 // auto-committing implicit transactions that were started by query() via DBO_TRX 03214 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " . 03215 " performing implicit commit!"; 03216 wfWarn( $msg ); 03217 wfLogDBError( $msg ); 03218 } else { 03219 // if the transaction was automatic and has done write operations, 03220 // log it if $wgDebugDBTransactions is enabled. 03221 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { 03222 wfDebug( "$fname: Automatic transaction with writes in progress" . 03223 " (from {$this->mTrxFname}), performing implicit commit!\n" 03224 ); 03225 } 03226 } 03227 03228 $this->runOnTransactionPreCommitCallbacks(); 03229 $this->doCommit( $fname ); 03230 if ( $this->mTrxDoneWrites ) { 03231 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); 03232 } 03233 $this->runOnTransactionIdleCallbacks(); 03234 } 03235 03236 $this->doBegin( $fname ); 03237 $this->mTrxFname = $fname; 03238 $this->mTrxDoneWrites = false; 03239 $this->mTrxAutomatic = false; 03240 } 03241 03248 protected function doBegin( $fname ) { 03249 $this->query( 'BEGIN', $fname ); 03250 $this->mTrxLevel = 1; 03251 } 03252 03265 final public function commit( $fname = __METHOD__, $flush = '' ) { 03266 if ( $flush != 'flush' ) { 03267 if ( !$this->mTrxLevel ) { 03268 wfWarn( "$fname: No transaction to commit, something got out of sync!" ); 03269 } elseif ( $this->mTrxAutomatic ) { 03270 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); 03271 } 03272 } else { 03273 if ( !$this->mTrxLevel ) { 03274 return; // nothing to do 03275 } elseif ( !$this->mTrxAutomatic ) { 03276 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03277 } 03278 } 03279 03280 $this->runOnTransactionPreCommitCallbacks(); 03281 $this->doCommit( $fname ); 03282 if ( $this->mTrxDoneWrites ) { 03283 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); 03284 } 03285 $this->mTrxDoneWrites = false; 03286 $this->runOnTransactionIdleCallbacks(); 03287 } 03288 03295 protected function doCommit( $fname ) { 03296 if ( $this->mTrxLevel ) { 03297 $this->query( 'COMMIT', $fname ); 03298 $this->mTrxLevel = 0; 03299 } 03300 } 03301 03310 final public function rollback( $fname = __METHOD__ ) { 03311 if ( !$this->mTrxLevel ) { 03312 wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); 03313 } 03314 $this->doRollback( $fname ); 03315 $this->mTrxIdleCallbacks = array(); // cancel 03316 $this->mTrxPreCommitCallbacks = array(); // cancel 03317 if ( $this->mTrxDoneWrites ) { 03318 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); 03319 } 03320 $this->mTrxDoneWrites = false; 03321 } 03322 03329 protected function doRollback( $fname ) { 03330 if ( $this->mTrxLevel ) { 03331 $this->query( 'ROLLBACK', $fname, true ); 03332 $this->mTrxLevel = 0; 03333 } 03334 } 03335 03351 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 03352 $fname = __METHOD__ 03353 ) { 03354 throw new MWException( 03355 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 03356 } 03357 03365 function listTables( $prefix = null, $fname = __METHOD__ ) { 03366 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 03367 } 03368 03373 final public function clearViewsCache() { 03374 $this->allViews = null; 03375 } 03376 03388 public function listViews( $prefix = null, $fname = __METHOD__ ) { 03389 throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' ); 03390 } 03391 03399 public function isView( $name ) { 03400 throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' ); 03401 } 03402 03414 public function timestamp( $ts = 0 ) { 03415 return wfTimestamp( TS_MW, $ts ); 03416 } 03417 03431 public function timestampOrNull( $ts = null ) { 03432 if ( is_null( $ts ) ) { 03433 return null; 03434 } else { 03435 return $this->timestamp( $ts ); 03436 } 03437 } 03438 03454 public function resultObject( $result ) { 03455 if ( empty( $result ) ) { 03456 return false; 03457 } elseif ( $result instanceof ResultWrapper ) { 03458 return $result; 03459 } elseif ( $result === true ) { 03460 // Successful write query 03461 return $result; 03462 } else { 03463 return new ResultWrapper( $this, $result ); 03464 } 03465 } 03466 03472 public function ping() { 03473 # Stub. Not essential to override. 03474 return true; 03475 } 03476 03486 public function getLag() { 03487 return intval( $this->mFakeSlaveLag ); 03488 } 03489 03495 function maxListLen() { 03496 return 0; 03497 } 03498 03507 public function encodeBlob( $b ) { 03508 return $b; 03509 } 03510 03518 public function decodeBlob( $b ) { 03519 return $b; 03520 } 03521 03532 public function setSessionOptions( array $options ) { 03533 } 03534 03551 public function sourceFile( 03552 $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false 03553 ) { 03554 wfSuppressWarnings(); 03555 $fp = fopen( $filename, 'r' ); 03556 wfRestoreWarnings(); 03557 03558 if ( false === $fp ) { 03559 throw new MWException( "Could not open \"{$filename}\".\n" ); 03560 } 03561 03562 if ( !$fname ) { 03563 $fname = __METHOD__ . "( $filename )"; 03564 } 03565 03566 try { 03567 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); 03568 } 03569 catch ( MWException $e ) { 03570 fclose( $fp ); 03571 throw $e; 03572 } 03573 03574 fclose( $fp ); 03575 03576 return $error; 03577 } 03578 03587 public function patchPath( $patch ) { 03588 global $IP; 03589 03590 $dbType = $this->getType(); 03591 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 03592 return "$IP/maintenance/$dbType/archives/$patch"; 03593 } else { 03594 return "$IP/maintenance/archives/$patch"; 03595 } 03596 } 03597 03605 public function setSchemaVars( $vars ) { 03606 $this->mSchemaVars = $vars; 03607 } 03608 03622 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 03623 $fname = __METHOD__, $inputCallback = false ) 03624 { 03625 $cmd = ''; 03626 03627 while ( !feof( $fp ) ) { 03628 if ( $lineCallback ) { 03629 call_user_func( $lineCallback ); 03630 } 03631 03632 $line = trim( fgets( $fp ) ); 03633 03634 if ( $line == '' ) { 03635 continue; 03636 } 03637 03638 if ( '-' == $line[0] && '-' == $line[1] ) { 03639 continue; 03640 } 03641 03642 if ( $cmd != '' ) { 03643 $cmd .= ' '; 03644 } 03645 03646 $done = $this->streamStatementEnd( $cmd, $line ); 03647 03648 $cmd .= "$line\n"; 03649 03650 if ( $done || feof( $fp ) ) { 03651 $cmd = $this->replaceVars( $cmd ); 03652 03653 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { 03654 $res = $this->query( $cmd, $fname ); 03655 03656 if ( $resultCallback ) { 03657 call_user_func( $resultCallback, $res, $this ); 03658 } 03659 03660 if ( false === $res ) { 03661 $err = $this->lastError(); 03662 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 03663 } 03664 } 03665 $cmd = ''; 03666 } 03667 } 03668 03669 return true; 03670 } 03671 03679 public function streamStatementEnd( &$sql, &$newLine ) { 03680 if ( $this->delimiter ) { 03681 $prev = $newLine; 03682 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 03683 if ( $newLine != $prev ) { 03684 return true; 03685 } 03686 } 03687 return false; 03688 } 03689 03707 protected function replaceSchemaVars( $ins ) { 03708 $vars = $this->getSchemaVars(); 03709 foreach ( $vars as $var => $value ) { 03710 // replace '{$var}' 03711 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 03712 // replace `{$var}` 03713 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 03714 // replace /*$var*/ 03715 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); 03716 } 03717 return $ins; 03718 } 03719 03727 protected function replaceVars( $ins ) { 03728 $ins = $this->replaceSchemaVars( $ins ); 03729 03730 // Table prefixes 03731 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 03732 array( $this, 'tableNameCallback' ), $ins ); 03733 03734 // Index names 03735 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 03736 array( $this, 'indexNameCallback' ), $ins ); 03737 03738 return $ins; 03739 } 03740 03747 protected function getSchemaVars() { 03748 if ( $this->mSchemaVars ) { 03749 return $this->mSchemaVars; 03750 } else { 03751 return $this->getDefaultSchemaVars(); 03752 } 03753 } 03754 03763 protected function getDefaultSchemaVars() { 03764 return array(); 03765 } 03766 03774 protected function tableNameCallback( $matches ) { 03775 return $this->tableName( $matches[1] ); 03776 } 03777 03785 protected function indexNameCallback( $matches ) { 03786 return $this->indexName( $matches[1] ); 03787 } 03788 03797 public function lockIsFree( $lockName, $method ) { 03798 return true; 03799 } 03800 03812 public function lock( $lockName, $method, $timeout = 5 ) { 03813 return true; 03814 } 03815 03826 public function unlock( $lockName, $method ) { 03827 return true; 03828 } 03829 03840 public function lockTables( $read, $write, $method, $lowPriority = true ) { 03841 return true; 03842 } 03843 03851 public function unlockTables( $method ) { 03852 return true; 03853 } 03854 03862 public function dropTable( $tableName, $fName = __METHOD__ ) { 03863 if ( !$this->tableExists( $tableName, $fName ) ) { 03864 return false; 03865 } 03866 $sql = "DROP TABLE " . $this->tableName( $tableName ); 03867 if ( $this->cascadingDeletes() ) { 03868 $sql .= " CASCADE"; 03869 } 03870 return $this->query( $sql, $fName ); 03871 } 03872 03879 public function getSearchEngine() { 03880 return 'SearchEngineDummy'; 03881 } 03882 03890 public function getInfinity() { 03891 return 'infinity'; 03892 } 03893 03900 public function encodeExpiry( $expiry ) { 03901 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 03902 ? $this->getInfinity() 03903 : $this->timestamp( $expiry ); 03904 } 03905 03913 public function decodeExpiry( $expiry, $format = TS_MW ) { 03914 return ( $expiry == '' || $expiry == $this->getInfinity() ) 03915 ? 'infinity' 03916 : wfTimestamp( $format, $expiry ); 03917 } 03918 03928 public function setBigSelects( $value = true ) { 03929 // no-op 03930 } 03931 03935 public function __toString() { 03936 return (string)$this->mConn; 03937 } 03938 03942 public function __destruct() { 03943 if ( $this->mTrxLevel && $this->mTrxDoneWrites ) { 03944 trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." ); 03945 } 03946 if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) { 03947 $callers = array(); 03948 foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) { 03949 $callers[] = $callbackInfo[1]; 03950 03951 } 03952 $callers = implode( ', ', $callers ); 03953 trigger_error( "DB transaction callbacks still pending (from $callers)." ); 03954 } 03955 } 03956 }