MediaWiki
REL1_24
|
00001 <?php 00002 00035 interface DatabaseType { 00041 function getType(); 00042 00053 function open( $server, $user, $password, $dbName ); 00054 00065 function fetchObject( $res ); 00066 00076 function fetchRow( $res ); 00077 00084 function numRows( $res ); 00085 00093 function numFields( $res ); 00094 00103 function fieldName( $res, $n ); 00104 00117 function insertId(); 00118 00126 function dataSeek( $res, $row ); 00127 00134 function lastErrno(); 00135 00142 function lastError(); 00143 00153 function fieldInfo( $table, $field ); 00154 00162 function indexInfo( $table, $index, $fname = __METHOD__ ); 00163 00170 function affectedRows(); 00171 00178 function strencode( $s ); 00179 00188 function getSoftwareLink(); 00189 00196 function getServerVersion(); 00197 00205 function getServerInfo(); 00206 } 00207 00212 interface IDatabase { 00213 } 00214 00219 abstract class DatabaseBase implements IDatabase, DatabaseType { 00221 const DEADLOCK_TRIES = 4; 00222 00224 const DEADLOCK_DELAY_MIN = 500000; 00225 00227 const DEADLOCK_DELAY_MAX = 1500000; 00228 00229 # ------------------------------------------------------------------------------ 00230 # Variables 00231 # ------------------------------------------------------------------------------ 00232 00233 protected $mLastQuery = ''; 00234 protected $mDoneWrites = false; 00235 protected $mPHPError = false; 00236 00237 protected $mServer, $mUser, $mPassword, $mDBname; 00238 00240 protected $mConn = null; 00241 protected $mOpened = false; 00242 00244 protected $mTrxIdleCallbacks = array(); 00246 protected $mTrxPreCommitCallbacks = array(); 00247 00248 protected $mTablePrefix; 00249 protected $mSchema; 00250 protected $mFlags; 00251 protected $mForeign; 00252 protected $mErrorCount = 0; 00253 protected $mLBInfo = array(); 00254 protected $mDefaultBigSelects = null; 00255 protected $mSchemaVars = false; 00256 00257 protected $preparedArgs; 00258 00259 protected $htmlErrors; 00260 00261 protected $delimiter = ';'; 00262 00269 protected $mTrxLevel = 0; 00270 00276 protected $mTrxShortId = ''; 00277 00285 private $mTrxFname = null; 00286 00293 private $mTrxDoneWrites = false; 00294 00301 private $mTrxAutomatic = false; 00302 00308 private $mTrxAtomicLevels; 00309 00315 private $mTrxAutomaticAtomic = false; 00316 00321 protected $fileHandle = null; 00322 00327 protected $allViews = null; 00328 00329 # ------------------------------------------------------------------------------ 00330 # Accessors 00331 # ------------------------------------------------------------------------------ 00332 # These optionally set a variable and return the previous state 00333 00341 public function getServerInfo() { 00342 return $this->getServerVersion(); 00343 } 00344 00348 public function getDelimiter() { 00349 return $this->delimiter; 00350 } 00351 00361 public function debug( $debug = null ) { 00362 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 00363 } 00364 00386 public function bufferResults( $buffer = null ) { 00387 if ( is_null( $buffer ) ) { 00388 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 00389 } else { 00390 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 00391 } 00392 } 00393 00406 public function ignoreErrors( $ignoreErrors = null ) { 00407 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 00408 } 00409 00418 public function trxLevel() { 00419 return $this->mTrxLevel; 00420 } 00421 00427 public function errorCount( $count = null ) { 00428 return wfSetVar( $this->mErrorCount, $count ); 00429 } 00430 00436 public function tablePrefix( $prefix = null ) { 00437 return wfSetVar( $this->mTablePrefix, $prefix ); 00438 } 00439 00445 public function dbSchema( $schema = null ) { 00446 return wfSetVar( $this->mSchema, $schema ); 00447 } 00448 00454 public function setFileHandle( $fh ) { 00455 $this->fileHandle = $fh; 00456 } 00457 00467 public function getLBInfo( $name = null ) { 00468 if ( is_null( $name ) ) { 00469 return $this->mLBInfo; 00470 } else { 00471 if ( array_key_exists( $name, $this->mLBInfo ) ) { 00472 return $this->mLBInfo[$name]; 00473 } else { 00474 return null; 00475 } 00476 } 00477 } 00478 00487 public function setLBInfo( $name, $value = null ) { 00488 if ( is_null( $value ) ) { 00489 $this->mLBInfo = $name; 00490 } else { 00491 $this->mLBInfo[$name] = $value; 00492 } 00493 } 00494 00502 public function setFakeSlaveLag( $lag ) { 00503 } 00504 00510 public function setFakeMaster( $enabled = true ) { 00511 } 00512 00518 public function cascadingDeletes() { 00519 return false; 00520 } 00521 00527 public function cleanupTriggers() { 00528 return false; 00529 } 00530 00537 public function strictIPs() { 00538 return false; 00539 } 00540 00546 public function realTimestamps() { 00547 return false; 00548 } 00549 00555 public function implicitGroupby() { 00556 return true; 00557 } 00558 00565 public function implicitOrderby() { 00566 return true; 00567 } 00568 00575 public function searchableIPs() { 00576 return false; 00577 } 00578 00584 public function functionalIndexes() { 00585 return false; 00586 } 00587 00592 public function lastQuery() { 00593 return $this->mLastQuery; 00594 } 00595 00602 public function doneWrites() { 00603 return (bool)$this->mDoneWrites; 00604 } 00605 00613 public function lastDoneWrites() { 00614 return $this->mDoneWrites ?: false; 00615 } 00616 00623 public function writesOrCallbacksPending() { 00624 return $this->mTrxLevel && ( 00625 $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks 00626 ); 00627 } 00628 00633 public function isOpen() { 00634 return $this->mOpened; 00635 } 00636 00648 public function setFlag( $flag ) { 00649 global $wgDebugDBTransactions; 00650 $this->mFlags |= $flag; 00651 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00652 wfDebug( "Implicit transactions are now enabled.\n" ); 00653 } 00654 } 00655 00667 public function clearFlag( $flag ) { 00668 global $wgDebugDBTransactions; 00669 $this->mFlags &= ~$flag; 00670 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00671 wfDebug( "Implicit transactions are now disabled.\n" ); 00672 } 00673 } 00674 00685 public function getFlag( $flag ) { 00686 return !!( $this->mFlags & $flag ); 00687 } 00688 00695 public function getProperty( $name ) { 00696 return $this->$name; 00697 } 00698 00702 public function getWikiID() { 00703 if ( $this->mTablePrefix ) { 00704 return "{$this->mDBname}-{$this->mTablePrefix}"; 00705 } else { 00706 return $this->mDBname; 00707 } 00708 } 00709 00717 private function getSqlFilePath( $filename ) { 00718 global $IP; 00719 $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename"; 00720 if ( file_exists( $dbmsSpecificFilePath ) ) { 00721 return $dbmsSpecificFilePath; 00722 } else { 00723 return "$IP/maintenance/$filename"; 00724 } 00725 } 00726 00733 public function getSchemaPath() { 00734 return $this->getSqlFilePath( 'tables.sql' ); 00735 } 00736 00743 public function getUpdateKeysPath() { 00744 return $this->getSqlFilePath( 'update-keys.sql' ); 00745 } 00746 00747 # ------------------------------------------------------------------------------ 00748 # Other functions 00749 # ------------------------------------------------------------------------------ 00750 00763 function __construct( $params = null ) { 00764 global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions; 00765 00766 $this->mTrxAtomicLevels = new SplStack; 00767 00768 if ( is_array( $params ) ) { // MW 1.22 00769 $server = $params['host']; 00770 $user = $params['user']; 00771 $password = $params['password']; 00772 $dbName = $params['dbname']; 00773 $flags = $params['flags']; 00774 $tablePrefix = $params['tablePrefix']; 00775 $schema = $params['schema']; 00776 $foreign = $params['foreign']; 00777 } else { // legacy calling pattern 00778 wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" ); 00779 $args = func_get_args(); 00780 $server = isset( $args[0] ) ? $args[0] : false; 00781 $user = isset( $args[1] ) ? $args[1] : false; 00782 $password = isset( $args[2] ) ? $args[2] : false; 00783 $dbName = isset( $args[3] ) ? $args[3] : false; 00784 $flags = isset( $args[4] ) ? $args[4] : 0; 00785 $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global'; 00786 $schema = 'get from global'; 00787 $foreign = isset( $args[6] ) ? $args[6] : false; 00788 } 00789 00790 $this->mFlags = $flags; 00791 if ( $this->mFlags & DBO_DEFAULT ) { 00792 if ( $wgCommandLineMode ) { 00793 $this->mFlags &= ~DBO_TRX; 00794 if ( $wgDebugDBTransactions ) { 00795 wfDebug( "Implicit transaction open disabled.\n" ); 00796 } 00797 } else { 00798 $this->mFlags |= DBO_TRX; 00799 if ( $wgDebugDBTransactions ) { 00800 wfDebug( "Implicit transaction open enabled.\n" ); 00801 } 00802 } 00803 } 00804 00806 if ( $tablePrefix == 'get from global' ) { 00807 $this->mTablePrefix = $wgDBprefix; 00808 } else { 00809 $this->mTablePrefix = $tablePrefix; 00810 } 00811 00813 if ( $schema == 'get from global' ) { 00814 $this->mSchema = $wgDBmwschema; 00815 } else { 00816 $this->mSchema = $schema; 00817 } 00818 00819 $this->mForeign = $foreign; 00820 00821 if ( $user ) { 00822 $this->open( $server, $user, $password, $dbName ); 00823 } 00824 } 00825 00831 public function __sleep() { 00832 throw new MWException( 'Database serialization may cause problems, since ' . 00833 'the connection is not restored on wakeup.' ); 00834 } 00835 00858 final public static function factory( $dbType, $p = array() ) { 00859 $canonicalDBTypes = array( 00860 'mysql' => array( 'mysqli', 'mysql' ), 00861 'postgres' => array(), 00862 'sqlite' => array(), 00863 'oracle' => array(), 00864 'mssql' => array(), 00865 ); 00866 00867 $driver = false; 00868 $dbType = strtolower( $dbType ); 00869 if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) { 00870 $possibleDrivers = $canonicalDBTypes[$dbType]; 00871 if ( !empty( $p['driver'] ) ) { 00872 if ( in_array( $p['driver'], $possibleDrivers ) ) { 00873 $driver = $p['driver']; 00874 } else { 00875 throw new MWException( __METHOD__ . 00876 " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" ); 00877 } 00878 } else { 00879 foreach ( $possibleDrivers as $posDriver ) { 00880 if ( extension_loaded( $posDriver ) ) { 00881 $driver = $posDriver; 00882 break; 00883 } 00884 } 00885 } 00886 } else { 00887 $driver = $dbType; 00888 } 00889 if ( $driver === false ) { 00890 throw new MWException( __METHOD__ . 00891 " no viable database extension found for type '$dbType'" ); 00892 } 00893 00894 // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema, 00895 // and everything else doesn't use a schema (e.g. null) 00896 // Although postgres and oracle support schemas, we don't use them (yet) 00897 // to maintain backwards compatibility 00898 $defaultSchemas = array( 00899 'mysql' => null, 00900 'postgres' => null, 00901 'sqlite' => null, 00902 'oracle' => null, 00903 'mssql' => 'get from global', 00904 ); 00905 00906 $class = 'Database' . ucfirst( $driver ); 00907 if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) { 00908 $params = array( 00909 'host' => isset( $p['host'] ) ? $p['host'] : false, 00910 'user' => isset( $p['user'] ) ? $p['user'] : false, 00911 'password' => isset( $p['password'] ) ? $p['password'] : false, 00912 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false, 00913 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0, 00914 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global', 00915 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType], 00916 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false 00917 ); 00918 00919 return new $class( $params ); 00920 } else { 00921 return null; 00922 } 00923 } 00924 00925 protected function installErrorHandler() { 00926 $this->mPHPError = false; 00927 $this->htmlErrors = ini_set( 'html_errors', '0' ); 00928 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 00929 } 00930 00934 protected function restoreErrorHandler() { 00935 restore_error_handler(); 00936 if ( $this->htmlErrors !== false ) { 00937 ini_set( 'html_errors', $this->htmlErrors ); 00938 } 00939 if ( $this->mPHPError ) { 00940 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 00941 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 00942 00943 return $error; 00944 } else { 00945 return false; 00946 } 00947 } 00948 00953 public function connectionErrorHandler( $errno, $errstr ) { 00954 $this->mPHPError = $errstr; 00955 } 00956 00964 public function close() { 00965 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 00966 throw new MWException( "Transaction idle callbacks still pending." ); 00967 } 00968 if ( $this->mConn ) { 00969 if ( $this->trxLevel() ) { 00970 if ( !$this->mTrxAutomatic ) { 00971 wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . 00972 " performing implicit commit before closing connection!" ); 00973 } 00974 00975 $this->commit( __METHOD__, 'flush' ); 00976 } 00977 00978 $closed = $this->closeConnection(); 00979 $this->mConn = false; 00980 } else { 00981 $closed = true; 00982 } 00983 $this->mOpened = false; 00984 00985 return $closed; 00986 } 00987 00993 abstract protected function closeConnection(); 00994 00999 function reportConnectionError( $error = 'Unknown error' ) { 01000 $myError = $this->lastError(); 01001 if ( $myError ) { 01002 $error = $myError; 01003 } 01004 01005 # New method 01006 throw new DBConnectionError( $this, $error ); 01007 } 01008 01016 abstract protected function doQuery( $sql ); 01017 01025 public function isWriteQuery( $sql ) { 01026 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 01027 } 01028 01051 public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { 01052 global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength; 01053 01054 $this->mLastQuery = $sql; 01055 if ( $this->isWriteQuery( $sql ) ) { 01056 # Set a flag indicating that writes have been done 01057 wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" ); 01058 $this->mDoneWrites = microtime( true ); 01059 } 01060 01061 # Add a comment for easy SHOW PROCESSLIST interpretation 01062 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 01063 $userName = $wgUser->getName(); 01064 if ( mb_strlen( $userName ) > 15 ) { 01065 $userName = mb_substr( $userName, 0, 15 ) . '...'; 01066 } 01067 $userName = str_replace( '/', '', $userName ); 01068 } else { 01069 $userName = ''; 01070 } 01071 01072 // Add trace comment to the begin of the sql string, right after the operator. 01073 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) 01074 $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 ); 01075 01076 # If DBO_TRX is set, start a transaction 01077 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && 01078 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' 01079 ) { 01080 # Avoid establishing transactions for SHOW and SET statements too - 01081 # that would delay transaction initializations to once connection 01082 # is really used by application 01083 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 01084 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 01085 if ( $wgDebugDBTransactions ) { 01086 wfDebug( "Implicit transaction start.\n" ); 01087 } 01088 $this->begin( __METHOD__ . " ($fname)" ); 01089 $this->mTrxAutomatic = true; 01090 } 01091 } 01092 01093 # Keep track of whether the transaction has write queries pending 01094 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { 01095 $this->mTrxDoneWrites = true; 01096 Profiler::instance()->transactionWritingIn( 01097 $this->mServer, $this->mDBname, $this->mTrxShortId ); 01098 } 01099 01100 $queryProf = ''; 01101 $totalProf = ''; 01102 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 01103 01104 if ( !Profiler::instance()->isStub() ) { 01105 # generalizeSQL will probably cut down the query to reasonable 01106 # logging size most of the time. The substr is really just a sanity check. 01107 if ( $isMaster ) { 01108 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 01109 $totalProf = 'DatabaseBase::query-master'; 01110 } else { 01111 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 01112 $totalProf = 'DatabaseBase::query'; 01113 } 01114 # Include query transaction state 01115 $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : ""; 01116 01117 $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no'; 01118 wfProfileIn( $totalProf ); 01119 wfProfileIn( $queryProf ); 01120 } 01121 01122 if ( $this->debug() ) { 01123 static $cnt = 0; 01124 01125 $cnt++; 01126 $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength ) 01127 : $commentedSql; 01128 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 01129 01130 $master = $isMaster ? 'master' : 'slave'; 01131 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 01132 } 01133 01134 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 01135 01136 # Avoid fatals if close() was called 01137 if ( !$this->isOpen() ) { 01138 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 01139 } 01140 01141 # Do the query and handle errors 01142 $ret = $this->doQuery( $commentedSql ); 01143 01144 MWDebug::queryTime( $queryId ); 01145 01146 # Try reconnecting if the connection was lost 01147 if ( false === $ret && $this->wasErrorReissuable() ) { 01148 # Transaction is gone, like it or not 01149 $hadTrx = $this->mTrxLevel; // possible lost transaction 01150 $this->mTrxLevel = 0; 01151 $this->mTrxIdleCallbacks = array(); // bug 65263 01152 $this->mTrxPreCommitCallbacks = array(); // bug 65263 01153 wfDebug( "Connection lost, reconnecting...\n" ); 01154 # Stash the last error values since ping() might clear them 01155 $lastError = $this->lastError(); 01156 $lastErrno = $this->lastErrno(); 01157 if ( $this->ping() ) { 01158 global $wgRequestTime; 01159 wfDebug( "Reconnected\n" ); 01160 $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength ) 01161 : $commentedSql; 01162 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 01163 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 01164 if ( $elapsed < 300 ) { 01165 # Not a database error to lose a transaction after a minute or two 01166 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" ); 01167 } 01168 if ( $hadTrx ) { 01169 # Leave $ret as false and let an error be reported. 01170 # Callers may catch the exception and continue to use the DB. 01171 $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore ); 01172 } else { 01173 # Should be safe to silently retry (no trx and thus no callbacks) 01174 $ret = $this->doQuery( $commentedSql ); 01175 } 01176 } else { 01177 wfDebug( "Failed\n" ); 01178 } 01179 } 01180 01181 if ( false === $ret ) { 01182 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 01183 } 01184 01185 if ( !Profiler::instance()->isStub() ) { 01186 wfProfileOut( $queryProf ); 01187 wfProfileOut( $totalProf ); 01188 } 01189 01190 return $this->resultObject( $ret ); 01191 } 01192 01204 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 01205 # Ignore errors during error handling to avoid infinite recursion 01206 $ignore = $this->ignoreErrors( true ); 01207 ++$this->mErrorCount; 01208 01209 if ( $ignore || $tempIgnore ) { 01210 wfDebug( "SQL ERROR (ignored): $error\n" ); 01211 $this->ignoreErrors( $ignore ); 01212 } else { 01213 $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ); 01214 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" ); 01215 wfDebug( "SQL ERROR: " . $error . "\n" ); 01216 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 01217 } 01218 } 01219 01234 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 01235 /* MySQL doesn't support prepared statements (yet), so just 01236 * pack up the query for reference. We'll manually replace 01237 * the bits later. 01238 */ 01239 return array( 'query' => $sql, 'func' => $func ); 01240 } 01241 01246 protected function freePrepared( $prepared ) { 01247 /* No-op by default */ 01248 } 01249 01257 public function execute( $prepared, $args = null ) { 01258 if ( !is_array( $args ) ) { 01259 # Pull the var args 01260 $args = func_get_args(); 01261 array_shift( $args ); 01262 } 01263 01264 $sql = $this->fillPrepared( $prepared['query'], $args ); 01265 01266 return $this->query( $sql, $prepared['func'] ); 01267 } 01268 01276 public function fillPrepared( $preparedQuery, $args ) { 01277 reset( $args ); 01278 $this->preparedArgs =& $args; 01279 01280 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 01281 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 01282 } 01283 01293 protected function fillPreparedArg( $matches ) { 01294 switch ( $matches[1] ) { 01295 case '\\?': 01296 return '?'; 01297 case '\\!': 01298 return '!'; 01299 case '\\&': 01300 return '&'; 01301 } 01302 01303 list( /* $n */, $arg ) = each( $this->preparedArgs ); 01304 01305 switch ( $matches[1] ) { 01306 case '?': 01307 return $this->addQuotes( $arg ); 01308 case '!': 01309 return $arg; 01310 case '&': 01311 # return $this->addQuotes( file_get_contents( $arg ) ); 01312 throw new DBUnexpectedError( 01313 $this, 01314 '& mode is not implemented. If it\'s really needed, uncomment the line above.' 01315 ); 01316 default: 01317 throw new DBUnexpectedError( 01318 $this, 01319 'Received invalid match. This should never happen!' 01320 ); 01321 } 01322 } 01323 01331 public function freeResult( $res ) { 01332 } 01333 01351 public function selectField( $table, $var, $cond = '', $fname = __METHOD__, 01352 $options = array() 01353 ) { 01354 if ( !is_array( $options ) ) { 01355 $options = array( $options ); 01356 } 01357 01358 $options['LIMIT'] = 1; 01359 01360 $res = $this->select( $table, $var, $cond, $fname, $options ); 01361 01362 if ( $res === false || !$this->numRows( $res ) ) { 01363 return false; 01364 } 01365 01366 $row = $this->fetchRow( $res ); 01367 01368 if ( $row !== false ) { 01369 return reset( $row ); 01370 } else { 01371 return false; 01372 } 01373 } 01374 01384 public function makeSelectOptions( $options ) { 01385 $preLimitTail = $postLimitTail = ''; 01386 $startOpts = ''; 01387 01388 $noKeyOptions = array(); 01389 01390 foreach ( $options as $key => $option ) { 01391 if ( is_numeric( $key ) ) { 01392 $noKeyOptions[$option] = true; 01393 } 01394 } 01395 01396 $preLimitTail .= $this->makeGroupByWithHaving( $options ); 01397 01398 $preLimitTail .= $this->makeOrderBy( $options ); 01399 01400 // if (isset($options['LIMIT'])) { 01401 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 01402 // isset($options['OFFSET']) ? $options['OFFSET'] 01403 // : false); 01404 // } 01405 01406 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 01407 $postLimitTail .= ' FOR UPDATE'; 01408 } 01409 01410 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 01411 $postLimitTail .= ' LOCK IN SHARE MODE'; 01412 } 01413 01414 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01415 $startOpts .= 'DISTINCT'; 01416 } 01417 01418 # Various MySQL extensions 01419 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 01420 $startOpts .= ' /*! STRAIGHT_JOIN */'; 01421 } 01422 01423 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 01424 $startOpts .= ' HIGH_PRIORITY'; 01425 } 01426 01427 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 01428 $startOpts .= ' SQL_BIG_RESULT'; 01429 } 01430 01431 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 01432 $startOpts .= ' SQL_BUFFER_RESULT'; 01433 } 01434 01435 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 01436 $startOpts .= ' SQL_SMALL_RESULT'; 01437 } 01438 01439 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 01440 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 01441 } 01442 01443 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 01444 $startOpts .= ' SQL_CACHE'; 01445 } 01446 01447 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 01448 $startOpts .= ' SQL_NO_CACHE'; 01449 } 01450 01451 if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) { 01452 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01453 } else { 01454 $useIndex = ''; 01455 } 01456 01457 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01458 } 01459 01468 public function makeGroupByWithHaving( $options ) { 01469 $sql = ''; 01470 if ( isset( $options['GROUP BY'] ) ) { 01471 $gb = is_array( $options['GROUP BY'] ) 01472 ? implode( ',', $options['GROUP BY'] ) 01473 : $options['GROUP BY']; 01474 $sql .= ' GROUP BY ' . $gb; 01475 } 01476 if ( isset( $options['HAVING'] ) ) { 01477 $having = is_array( $options['HAVING'] ) 01478 ? $this->makeList( $options['HAVING'], LIST_AND ) 01479 : $options['HAVING']; 01480 $sql .= ' HAVING ' . $having; 01481 } 01482 01483 return $sql; 01484 } 01485 01494 public function makeOrderBy( $options ) { 01495 if ( isset( $options['ORDER BY'] ) ) { 01496 $ob = is_array( $options['ORDER BY'] ) 01497 ? implode( ',', $options['ORDER BY'] ) 01498 : $options['ORDER BY']; 01499 01500 return ' ORDER BY ' . $ob; 01501 } 01502 01503 return ''; 01504 } 01505 01646 public function select( $table, $vars, $conds = '', $fname = __METHOD__, 01647 $options = array(), $join_conds = array() ) { 01648 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 01649 01650 return $this->query( $sql, $fname ); 01651 } 01652 01669 public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, 01670 $options = array(), $join_conds = array() 01671 ) { 01672 if ( is_array( $vars ) ) { 01673 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 01674 } 01675 01676 $options = (array)$options; 01677 $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 01678 ? $options['USE INDEX'] 01679 : array(); 01680 01681 if ( is_array( $table ) ) { 01682 $from = ' FROM ' . 01683 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds ); 01684 } elseif ( $table != '' ) { 01685 if ( $table[0] == ' ' ) { 01686 $from = ' FROM ' . $table; 01687 } else { 01688 $from = ' FROM ' . 01689 $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() ); 01690 } 01691 } else { 01692 $from = ''; 01693 } 01694 01695 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = 01696 $this->makeSelectOptions( $options ); 01697 01698 if ( !empty( $conds ) ) { 01699 if ( is_array( $conds ) ) { 01700 $conds = $this->makeList( $conds, LIST_AND ); 01701 } 01702 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 01703 } else { 01704 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 01705 } 01706 01707 if ( isset( $options['LIMIT'] ) ) { 01708 $sql = $this->limitResult( $sql, $options['LIMIT'], 01709 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 01710 } 01711 $sql = "$sql $postLimitTail"; 01712 01713 if ( isset( $options['EXPLAIN'] ) ) { 01714 $sql = 'EXPLAIN ' . $sql; 01715 } 01716 01717 return $sql; 01718 } 01719 01734 public function selectRow( $table, $vars, $conds, $fname = __METHOD__, 01735 $options = array(), $join_conds = array() 01736 ) { 01737 $options = (array)$options; 01738 $options['LIMIT'] = 1; 01739 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 01740 01741 if ( $res === false ) { 01742 return false; 01743 } 01744 01745 if ( !$this->numRows( $res ) ) { 01746 return false; 01747 } 01748 01749 $obj = $this->fetchObject( $res ); 01750 01751 return $obj; 01752 } 01753 01774 public function estimateRowCount( 01775 $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() 01776 ) { 01777 $rows = 0; 01778 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 01779 01780 if ( $res ) { 01781 $row = $this->fetchRow( $res ); 01782 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01783 } 01784 01785 return $rows; 01786 } 01787 01803 public function selectRowCount( 01804 $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() 01805 ) { 01806 $rows = 0; 01807 $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options ); 01808 $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" ); 01809 01810 if ( $res ) { 01811 $row = $this->fetchRow( $res ); 01812 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01813 } 01814 01815 return $rows; 01816 } 01817 01826 static function generalizeSQL( $sql ) { 01827 # This does the same as the regexp below would do, but in such a way 01828 # as to avoid crashing php on some large strings. 01829 # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql ); 01830 01831 $sql = str_replace( "\\\\", '', $sql ); 01832 $sql = str_replace( "\\'", '', $sql ); 01833 $sql = str_replace( "\\\"", '', $sql ); 01834 $sql = preg_replace( "/'.*'/s", "'X'", $sql ); 01835 $sql = preg_replace( '/".*"/s', "'X'", $sql ); 01836 01837 # All newlines, tabs, etc replaced by single space 01838 $sql = preg_replace( '/\s+/', ' ', $sql ); 01839 01840 # All numbers => N, 01841 # except the ones surrounded by characters, e.g. l10n 01842 $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql ); 01843 $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql ); 01844 01845 return $sql; 01846 } 01847 01856 public function fieldExists( $table, $field, $fname = __METHOD__ ) { 01857 $info = $this->fieldInfo( $table, $field ); 01858 01859 return (bool)$info; 01860 } 01861 01872 public function indexExists( $table, $index, $fname = __METHOD__ ) { 01873 if ( !$this->tableExists( $table ) ) { 01874 return null; 01875 } 01876 01877 $info = $this->indexInfo( $table, $index, $fname ); 01878 if ( is_null( $info ) ) { 01879 return null; 01880 } else { 01881 return $info !== false; 01882 } 01883 } 01884 01892 public function tableExists( $table, $fname = __METHOD__ ) { 01893 $table = $this->tableName( $table ); 01894 $old = $this->ignoreErrors( true ); 01895 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 01896 $this->ignoreErrors( $old ); 01897 01898 return (bool)$res; 01899 } 01900 01909 public function indexUnique( $table, $index ) { 01910 $indexInfo = $this->indexInfo( $table, $index ); 01911 01912 if ( !$indexInfo ) { 01913 return null; 01914 } 01915 01916 return !$indexInfo[0]->Non_unique; 01917 } 01918 01925 protected function makeInsertOptions( $options ) { 01926 return implode( ' ', $options ); 01927 } 01928 01962 public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { 01963 # No rows to insert, easy just return now 01964 if ( !count( $a ) ) { 01965 return true; 01966 } 01967 01968 $table = $this->tableName( $table ); 01969 01970 if ( !is_array( $options ) ) { 01971 $options = array( $options ); 01972 } 01973 01974 $fh = null; 01975 if ( isset( $options['fileHandle'] ) ) { 01976 $fh = $options['fileHandle']; 01977 } 01978 $options = $this->makeInsertOptions( $options ); 01979 01980 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 01981 $multi = true; 01982 $keys = array_keys( $a[0] ); 01983 } else { 01984 $multi = false; 01985 $keys = array_keys( $a ); 01986 } 01987 01988 $sql = 'INSERT ' . $options . 01989 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 01990 01991 if ( $multi ) { 01992 $first = true; 01993 foreach ( $a as $row ) { 01994 if ( $first ) { 01995 $first = false; 01996 } else { 01997 $sql .= ','; 01998 } 01999 $sql .= '(' . $this->makeList( $row ) . ')'; 02000 } 02001 } else { 02002 $sql .= '(' . $this->makeList( $a ) . ')'; 02003 } 02004 02005 if ( $fh !== null && false === fwrite( $fh, $sql ) ) { 02006 return false; 02007 } elseif ( $fh !== null ) { 02008 return true; 02009 } 02010 02011 return (bool)$this->query( $sql, $fname ); 02012 } 02013 02020 protected function makeUpdateOptionsArray( $options ) { 02021 if ( !is_array( $options ) ) { 02022 $options = array( $options ); 02023 } 02024 02025 $opts = array(); 02026 02027 if ( in_array( 'LOW_PRIORITY', $options ) ) { 02028 $opts[] = $this->lowPriorityOption(); 02029 } 02030 02031 if ( in_array( 'IGNORE', $options ) ) { 02032 $opts[] = 'IGNORE'; 02033 } 02034 02035 return $opts; 02036 } 02037 02044 protected function makeUpdateOptions( $options ) { 02045 $opts = $this->makeUpdateOptionsArray( $options ); 02046 02047 return implode( ' ', $opts ); 02048 } 02049 02068 function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) { 02069 $table = $this->tableName( $table ); 02070 $opts = $this->makeUpdateOptions( $options ); 02071 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 02072 02073 if ( $conds !== array() && $conds !== '*' ) { 02074 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 02075 } 02076 02077 return $this->query( $sql, $fname ); 02078 } 02079 02094 public function makeList( $a, $mode = LIST_COMMA ) { 02095 if ( !is_array( $a ) ) { 02096 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 02097 } 02098 02099 $first = true; 02100 $list = ''; 02101 02102 foreach ( $a as $field => $value ) { 02103 if ( !$first ) { 02104 if ( $mode == LIST_AND ) { 02105 $list .= ' AND '; 02106 } elseif ( $mode == LIST_OR ) { 02107 $list .= ' OR '; 02108 } else { 02109 $list .= ','; 02110 } 02111 } else { 02112 $first = false; 02113 } 02114 02115 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 02116 $list .= "($value)"; 02117 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 02118 $list .= "$value"; 02119 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 02120 if ( count( $value ) == 0 ) { 02121 throw new MWException( __METHOD__ . ": empty input for field $field" ); 02122 } elseif ( count( $value ) == 1 ) { 02123 // Special-case single values, as IN isn't terribly efficient 02124 // Don't necessarily assume the single key is 0; we don't 02125 // enforce linear numeric ordering on other arrays here. 02126 $value = array_values( $value ); 02127 $list .= $field . " = " . $this->addQuotes( $value[0] ); 02128 } else { 02129 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 02130 } 02131 } elseif ( $value === null ) { 02132 if ( $mode == LIST_AND || $mode == LIST_OR ) { 02133 $list .= "$field IS "; 02134 } elseif ( $mode == LIST_SET ) { 02135 $list .= "$field = "; 02136 } 02137 $list .= 'NULL'; 02138 } else { 02139 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 02140 $list .= "$field = "; 02141 } 02142 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 02143 } 02144 } 02145 02146 return $list; 02147 } 02148 02159 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 02160 $conds = array(); 02161 02162 foreach ( $data as $base => $sub ) { 02163 if ( count( $sub ) ) { 02164 $conds[] = $this->makeList( 02165 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 02166 LIST_AND ); 02167 } 02168 } 02169 02170 if ( $conds ) { 02171 return $this->makeList( $conds, LIST_OR ); 02172 } else { 02173 // Nothing to search for... 02174 return false; 02175 } 02176 } 02177 02186 public function aggregateValue( $valuedata, $valuename = 'value' ) { 02187 return $valuename; 02188 } 02189 02194 public function bitNot( $field ) { 02195 return "(~$field)"; 02196 } 02197 02203 public function bitAnd( $fieldLeft, $fieldRight ) { 02204 return "($fieldLeft & $fieldRight)"; 02205 } 02206 02212 public function bitOr( $fieldLeft, $fieldRight ) { 02213 return "($fieldLeft | $fieldRight)"; 02214 } 02215 02222 public function buildConcat( $stringList ) { 02223 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 02224 } 02225 02242 public function buildGroupConcatField( 02243 $delim, $table, $field, $conds = '', $join_conds = array() 02244 ) { 02245 $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')'; 02246 02247 return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')'; 02248 } 02249 02259 public function selectDB( $db ) { 02260 # Stub. Shouldn't cause serious problems if it's not overridden, but 02261 # if your database engine supports a concept similar to MySQL's 02262 # databases you may as well. 02263 $this->mDBname = $db; 02264 02265 return true; 02266 } 02267 02272 public function getDBname() { 02273 return $this->mDBname; 02274 } 02275 02280 public function getServer() { 02281 return $this->mServer; 02282 } 02283 02301 public function tableName( $name, $format = 'quoted' ) { 02302 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema; 02303 # Skip the entire process when we have a string quoted on both ends. 02304 # Note that we check the end so that we will still quote any use of 02305 # use of `database`.table. But won't break things if someone wants 02306 # to query a database table with a dot in the name. 02307 if ( $this->isQuotedIdentifier( $name ) ) { 02308 return $name; 02309 } 02310 02311 # Lets test for any bits of text that should never show up in a table 02312 # name. Basically anything like JOIN or ON which are actually part of 02313 # SQL queries, but may end up inside of the table value to combine 02314 # sql. Such as how the API is doing. 02315 # Note that we use a whitespace test rather than a \b test to avoid 02316 # any remote case where a word like on may be inside of a table name 02317 # surrounded by symbols which may be considered word breaks. 02318 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 02319 return $name; 02320 } 02321 02322 # Split database and table into proper variables. 02323 # We reverse the explode so that database.table and table both output 02324 # the correct table. 02325 $dbDetails = explode( '.', $name, 2 ); 02326 if ( count( $dbDetails ) == 3 ) { 02327 list( $database, $schema, $table ) = $dbDetails; 02328 # We don't want any prefix added in this case 02329 $prefix = ''; 02330 } elseif ( count( $dbDetails ) == 2 ) { 02331 list( $database, $table ) = $dbDetails; 02332 # We don't want any prefix added in this case 02333 # In dbs that support it, $database may actually be the schema 02334 # but that doesn't affect any of the functionality here 02335 $prefix = ''; 02336 $schema = null; 02337 } else { 02338 list( $table ) = $dbDetails; 02339 if ( $wgSharedDB !== null # We have a shared database 02340 && $this->mForeign == false # We're not working on a foreign database 02341 && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`' 02342 && in_array( $table, $wgSharedTables ) # A shared table is selected 02343 ) { 02344 $database = $wgSharedDB; 02345 $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema; 02346 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; 02347 } else { 02348 $database = null; 02349 $schema = $this->mSchema; # Default schema 02350 $prefix = $this->mTablePrefix; # Default prefix 02351 } 02352 } 02353 02354 # Quote $table and apply the prefix if not quoted. 02355 # $tableName might be empty if this is called from Database::replaceVars() 02356 $tableName = "{$prefix}{$table}"; 02357 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) { 02358 $tableName = $this->addIdentifierQuotes( $tableName ); 02359 } 02360 02361 # Quote $schema and merge it with the table name if needed 02362 if ( $schema !== null ) { 02363 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) { 02364 $schema = $this->addIdentifierQuotes( $schema ); 02365 } 02366 $tableName = $schema . '.' . $tableName; 02367 } 02368 02369 # Quote $database and merge it with the table name if needed 02370 if ( $database !== null ) { 02371 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 02372 $database = $this->addIdentifierQuotes( $database ); 02373 } 02374 $tableName = $database . '.' . $tableName; 02375 } 02376 02377 return $tableName; 02378 } 02379 02391 public function tableNames() { 02392 $inArray = func_get_args(); 02393 $retVal = array(); 02394 02395 foreach ( $inArray as $name ) { 02396 $retVal[$name] = $this->tableName( $name ); 02397 } 02398 02399 return $retVal; 02400 } 02401 02413 public function tableNamesN() { 02414 $inArray = func_get_args(); 02415 $retVal = array(); 02416 02417 foreach ( $inArray as $name ) { 02418 $retVal[] = $this->tableName( $name ); 02419 } 02420 02421 return $retVal; 02422 } 02423 02432 public function tableNameWithAlias( $name, $alias = false ) { 02433 if ( !$alias || $alias == $name ) { 02434 return $this->tableName( $name ); 02435 } else { 02436 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 02437 } 02438 } 02439 02446 public function tableNamesWithAlias( $tables ) { 02447 $retval = array(); 02448 foreach ( $tables as $alias => $table ) { 02449 if ( is_numeric( $alias ) ) { 02450 $alias = $table; 02451 } 02452 $retval[] = $this->tableNameWithAlias( $table, $alias ); 02453 } 02454 02455 return $retval; 02456 } 02457 02466 public function fieldNameWithAlias( $name, $alias = false ) { 02467 if ( !$alias || (string)$alias === (string)$name ) { 02468 return $name; 02469 } else { 02470 return $name . ' AS ' . $alias; //PostgreSQL needs AS 02471 } 02472 } 02473 02480 public function fieldNamesWithAlias( $fields ) { 02481 $retval = array(); 02482 foreach ( $fields as $alias => $field ) { 02483 if ( is_numeric( $alias ) ) { 02484 $alias = $field; 02485 } 02486 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 02487 } 02488 02489 return $retval; 02490 } 02491 02501 protected function tableNamesWithUseIndexOrJOIN( 02502 $tables, $use_index = array(), $join_conds = array() 02503 ) { 02504 $ret = array(); 02505 $retJOIN = array(); 02506 $use_index = (array)$use_index; 02507 $join_conds = (array)$join_conds; 02508 02509 foreach ( $tables as $alias => $table ) { 02510 if ( !is_string( $alias ) ) { 02511 // No alias? Set it equal to the table name 02512 $alias = $table; 02513 } 02514 // Is there a JOIN clause for this table? 02515 if ( isset( $join_conds[$alias] ) ) { 02516 list( $joinType, $conds ) = $join_conds[$alias]; 02517 $tableClause = $joinType; 02518 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 02519 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 02520 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 02521 if ( $use != '' ) { 02522 $tableClause .= ' ' . $use; 02523 } 02524 } 02525 $on = $this->makeList( (array)$conds, LIST_AND ); 02526 if ( $on != '' ) { 02527 $tableClause .= ' ON (' . $on . ')'; 02528 } 02529 02530 $retJOIN[] = $tableClause; 02531 } elseif ( isset( $use_index[$alias] ) ) { 02532 // Is there an INDEX clause for this table? 02533 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02534 $tableClause .= ' ' . $this->useIndexClause( 02535 implode( ',', (array)$use_index[$alias] ) 02536 ); 02537 02538 $ret[] = $tableClause; 02539 } else { 02540 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02541 02542 $ret[] = $tableClause; 02543 } 02544 } 02545 02546 // We can't separate explicit JOIN clauses with ',', use ' ' for those 02547 $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 02548 $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 02549 02550 // Compile our final table clause 02551 return implode( ' ', array( $implicitJoins, $explicitJoins ) ); 02552 } 02553 02560 protected function indexName( $index ) { 02561 // Backwards-compatibility hack 02562 $renamed = array( 02563 'ar_usertext_timestamp' => 'usertext_timestamp', 02564 'un_user_id' => 'user_id', 02565 'un_user_ip' => 'user_ip', 02566 ); 02567 02568 if ( isset( $renamed[$index] ) ) { 02569 return $renamed[$index]; 02570 } else { 02571 return $index; 02572 } 02573 } 02574 02581 public function addQuotes( $s ) { 02582 if ( $s === null ) { 02583 return 'NULL'; 02584 } else { 02585 # This will also quote numeric values. This should be harmless, 02586 # and protects against weird problems that occur when they really 02587 # _are_ strings such as article titles and string->number->string 02588 # conversion is not 1:1. 02589 return "'" . $this->strencode( $s ) . "'"; 02590 } 02591 } 02592 02602 public function addIdentifierQuotes( $s ) { 02603 return '"' . str_replace( '"', '""', $s ) . '"'; 02604 } 02605 02613 public function isQuotedIdentifier( $name ) { 02614 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 02615 } 02616 02621 protected function escapeLikeInternal( $s ) { 02622 return addcslashes( $s, '\%_' ); 02623 } 02624 02641 public function buildLike() { 02642 $params = func_get_args(); 02643 02644 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 02645 $params = $params[0]; 02646 } 02647 02648 $s = ''; 02649 02650 foreach ( $params as $value ) { 02651 if ( $value instanceof LikeMatch ) { 02652 $s .= $value->toString(); 02653 } else { 02654 $s .= $this->escapeLikeInternal( $value ); 02655 } 02656 } 02657 02658 return " LIKE {$this->addQuotes( $s )} "; 02659 } 02660 02666 public function anyChar() { 02667 return new LikeMatch( '_' ); 02668 } 02669 02675 public function anyString() { 02676 return new LikeMatch( '%' ); 02677 } 02678 02690 public function nextSequenceValue( $seqName ) { 02691 return null; 02692 } 02693 02704 public function useIndexClause( $index ) { 02705 return ''; 02706 } 02707 02730 public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { 02731 $quotedTable = $this->tableName( $table ); 02732 02733 if ( count( $rows ) == 0 ) { 02734 return; 02735 } 02736 02737 # Single row case 02738 if ( !is_array( reset( $rows ) ) ) { 02739 $rows = array( $rows ); 02740 } 02741 02742 foreach ( $rows as $row ) { 02743 # Delete rows which collide 02744 if ( $uniqueIndexes ) { 02745 $sql = "DELETE FROM $quotedTable WHERE "; 02746 $first = true; 02747 foreach ( $uniqueIndexes as $index ) { 02748 if ( $first ) { 02749 $first = false; 02750 $sql .= '( '; 02751 } else { 02752 $sql .= ' ) OR ( '; 02753 } 02754 if ( is_array( $index ) ) { 02755 $first2 = true; 02756 foreach ( $index as $col ) { 02757 if ( $first2 ) { 02758 $first2 = false; 02759 } else { 02760 $sql .= ' AND '; 02761 } 02762 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 02763 } 02764 } else { 02765 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 02766 } 02767 } 02768 $sql .= ' )'; 02769 $this->query( $sql, $fname ); 02770 } 02771 02772 # Now insert the row 02773 $this->insert( $table, $row, $fname ); 02774 } 02775 } 02776 02787 protected function nativeReplace( $table, $rows, $fname ) { 02788 $table = $this->tableName( $table ); 02789 02790 # Single row case 02791 if ( !is_array( reset( $rows ) ) ) { 02792 $rows = array( $rows ); 02793 } 02794 02795 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 02796 $first = true; 02797 02798 foreach ( $rows as $row ) { 02799 if ( $first ) { 02800 $first = false; 02801 } else { 02802 $sql .= ','; 02803 } 02804 02805 $sql .= '(' . $this->makeList( $row ) . ')'; 02806 } 02807 02808 return $this->query( $sql, $fname ); 02809 } 02810 02845 public function upsert( $table, array $rows, array $uniqueIndexes, array $set, 02846 $fname = __METHOD__ 02847 ) { 02848 if ( !count( $rows ) ) { 02849 return true; // nothing to do 02850 } 02851 02852 if ( !is_array( reset( $rows ) ) ) { 02853 $rows = array( $rows ); 02854 } 02855 02856 if ( count( $uniqueIndexes ) ) { 02857 $clauses = array(); // list WHERE clauses that each identify a single row 02858 foreach ( $rows as $row ) { 02859 foreach ( $uniqueIndexes as $index ) { 02860 $index = is_array( $index ) ? $index : array( $index ); // columns 02861 $rowKey = array(); // unique key to this row 02862 foreach ( $index as $column ) { 02863 $rowKey[$column] = $row[$column]; 02864 } 02865 $clauses[] = $this->makeList( $rowKey, LIST_AND ); 02866 } 02867 } 02868 $where = array( $this->makeList( $clauses, LIST_OR ) ); 02869 } else { 02870 $where = false; 02871 } 02872 02873 $useTrx = !$this->mTrxLevel; 02874 if ( $useTrx ) { 02875 $this->begin( $fname ); 02876 } 02877 try { 02878 # Update any existing conflicting row(s) 02879 if ( $where !== false ) { 02880 $ok = $this->update( $table, $set, $where, $fname ); 02881 } else { 02882 $ok = true; 02883 } 02884 # Now insert any non-conflicting row(s) 02885 $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok; 02886 } catch ( Exception $e ) { 02887 if ( $useTrx ) { 02888 $this->rollback( $fname ); 02889 } 02890 throw $e; 02891 } 02892 if ( $useTrx ) { 02893 $this->commit( $fname ); 02894 } 02895 02896 return $ok; 02897 } 02898 02919 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 02920 $fname = __METHOD__ 02921 ) { 02922 if ( !$conds ) { 02923 throw new DBUnexpectedError( $this, 02924 'DatabaseBase::deleteJoin() called with empty $conds' ); 02925 } 02926 02927 $delTable = $this->tableName( $delTable ); 02928 $joinTable = $this->tableName( $joinTable ); 02929 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 02930 if ( $conds != '*' ) { 02931 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 02932 } 02933 $sql .= ')'; 02934 02935 $this->query( $sql, $fname ); 02936 } 02937 02945 public function textFieldSize( $table, $field ) { 02946 $table = $this->tableName( $table ); 02947 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 02948 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 02949 $row = $this->fetchObject( $res ); 02950 02951 $m = array(); 02952 02953 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 02954 $size = $m[1]; 02955 } else { 02956 $size = -1; 02957 } 02958 02959 return $size; 02960 } 02961 02970 public function lowPriorityOption() { 02971 return ''; 02972 } 02973 02984 public function delete( $table, $conds, $fname = __METHOD__ ) { 02985 if ( !$conds ) { 02986 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 02987 } 02988 02989 $table = $this->tableName( $table ); 02990 $sql = "DELETE FROM $table"; 02991 02992 if ( $conds != '*' ) { 02993 if ( is_array( $conds ) ) { 02994 $conds = $this->makeList( $conds, LIST_AND ); 02995 } 02996 $sql .= ' WHERE ' . $conds; 02997 } 02998 02999 return $this->query( $sql, $fname ); 03000 } 03001 03028 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 03029 $fname = __METHOD__, 03030 $insertOptions = array(), $selectOptions = array() 03031 ) { 03032 $destTable = $this->tableName( $destTable ); 03033 03034 if ( !is_array( $insertOptions ) ) { 03035 $insertOptions = array( $insertOptions ); 03036 } 03037 03038 $insertOptions = $this->makeInsertOptions( $insertOptions ); 03039 03040 if ( !is_array( $selectOptions ) ) { 03041 $selectOptions = array( $selectOptions ); 03042 } 03043 03044 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 03045 03046 if ( is_array( $srcTable ) ) { 03047 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 03048 } else { 03049 $srcTable = $this->tableName( $srcTable ); 03050 } 03051 03052 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 03053 " SELECT $startOpts " . implode( ',', $varMap ) . 03054 " FROM $srcTable $useIndex "; 03055 03056 if ( $conds != '*' ) { 03057 if ( is_array( $conds ) ) { 03058 $conds = $this->makeList( $conds, LIST_AND ); 03059 } 03060 $sql .= " WHERE $conds"; 03061 } 03062 03063 $sql .= " $tailOpts"; 03064 03065 return $this->query( $sql, $fname ); 03066 } 03067 03087 public function limitResult( $sql, $limit, $offset = false ) { 03088 if ( !is_numeric( $limit ) ) { 03089 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 03090 } 03091 03092 return "$sql LIMIT " 03093 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 03094 . "{$limit} "; 03095 } 03096 03102 public function unionSupportsOrderAndLimit() { 03103 return true; // True for almost every DB supported 03104 } 03105 03114 public function unionQueries( $sqls, $all ) { 03115 $glue = $all ? ') UNION ALL (' : ') UNION ('; 03116 03117 return '(' . implode( $glue, $sqls ) . ')'; 03118 } 03119 03129 public function conditional( $cond, $trueVal, $falseVal ) { 03130 if ( is_array( $cond ) ) { 03131 $cond = $this->makeList( $cond, LIST_AND ); 03132 } 03133 03134 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 03135 } 03136 03147 public function strreplace( $orig, $old, $new ) { 03148 return "REPLACE({$orig}, {$old}, {$new})"; 03149 } 03150 03157 public function getServerUptime() { 03158 return 0; 03159 } 03160 03167 public function wasDeadlock() { 03168 return false; 03169 } 03170 03177 public function wasLockTimeout() { 03178 return false; 03179 } 03180 03188 public function wasErrorReissuable() { 03189 return false; 03190 } 03191 03198 public function wasReadOnlyError() { 03199 return false; 03200 } 03201 03220 public function deadlockLoop() { 03221 $this->begin( __METHOD__ ); 03222 $args = func_get_args(); 03223 $function = array_shift( $args ); 03224 $oldIgnore = $this->ignoreErrors( true ); 03225 $tries = self::DEADLOCK_TRIES; 03226 03227 if ( is_array( $function ) ) { 03228 $fname = $function[0]; 03229 } else { 03230 $fname = $function; 03231 } 03232 03233 do { 03234 $retVal = call_user_func_array( $function, $args ); 03235 $error = $this->lastError(); 03236 $errno = $this->lastErrno(); 03237 $sql = $this->lastQuery(); 03238 03239 if ( $errno ) { 03240 if ( $this->wasDeadlock() ) { 03241 # Retry 03242 usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) ); 03243 } else { 03244 $this->reportQueryError( $error, $errno, $sql, $fname ); 03245 } 03246 } 03247 } while ( $this->wasDeadlock() && --$tries > 0 ); 03248 03249 $this->ignoreErrors( $oldIgnore ); 03250 03251 if ( $tries <= 0 ) { 03252 $this->rollback( __METHOD__ ); 03253 $this->reportQueryError( $error, $errno, $sql, $fname ); 03254 03255 return false; 03256 } else { 03257 $this->commit( __METHOD__ ); 03258 03259 return $retVal; 03260 } 03261 } 03262 03273 public function masterPosWait( DBMasterPos $pos, $timeout ) { 03274 # Real waits are implemented in the subclass. 03275 return 0; 03276 } 03277 03283 public function getSlavePos() { 03284 # Stub 03285 return false; 03286 } 03287 03293 public function getMasterPos() { 03294 # Stub 03295 return false; 03296 } 03297 03312 final public function onTransactionIdle( $callback ) { 03313 $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() ); 03314 if ( !$this->mTrxLevel ) { 03315 $this->runOnTransactionIdleCallbacks(); 03316 } 03317 } 03318 03330 final public function onTransactionPreCommitOrIdle( $callback ) { 03331 if ( $this->mTrxLevel ) { 03332 $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() ); 03333 } else { 03334 $this->onTransactionIdle( $callback ); // this will trigger immediately 03335 } 03336 } 03337 03343 protected function runOnTransactionIdleCallbacks() { 03344 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? 03345 03346 $e = $ePrior = null; // last exception 03347 do { // callbacks may add callbacks :) 03348 $callbacks = $this->mTrxIdleCallbacks; 03349 $this->mTrxIdleCallbacks = array(); // recursion guard 03350 foreach ( $callbacks as $callback ) { 03351 try { 03352 list( $phpCallback ) = $callback; 03353 $this->clearFlag( DBO_TRX ); // make each query its own transaction 03354 call_user_func( $phpCallback ); 03355 $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() 03356 } catch ( Exception $e ) { 03357 if ( $ePrior ) { 03358 MWExceptionHandler::logException( $ePrior ); 03359 } 03360 $ePrior = $e; 03361 } 03362 } 03363 } while ( count( $this->mTrxIdleCallbacks ) ); 03364 03365 if ( $e instanceof Exception ) { 03366 throw $e; // re-throw any last exception 03367 } 03368 } 03369 03375 protected function runOnTransactionPreCommitCallbacks() { 03376 $e = $ePrior = null; // last exception 03377 do { // callbacks may add callbacks :) 03378 $callbacks = $this->mTrxPreCommitCallbacks; 03379 $this->mTrxPreCommitCallbacks = array(); // recursion guard 03380 foreach ( $callbacks as $callback ) { 03381 try { 03382 list( $phpCallback ) = $callback; 03383 call_user_func( $phpCallback ); 03384 } catch ( Exception $e ) { 03385 if ( $ePrior ) { 03386 MWExceptionHandler::logException( $ePrior ); 03387 } 03388 $ePrior = $e; 03389 } 03390 } 03391 } while ( count( $this->mTrxPreCommitCallbacks ) ); 03392 03393 if ( $e instanceof Exception ) { 03394 throw $e; // re-throw any last exception 03395 } 03396 } 03397 03422 final public function startAtomic( $fname = __METHOD__ ) { 03423 if ( !$this->mTrxLevel ) { 03424 $this->begin( $fname ); 03425 $this->mTrxAutomatic = true; 03426 $this->mTrxAutomaticAtomic = true; 03427 } 03428 03429 $this->mTrxAtomicLevels->push( $fname ); 03430 } 03431 03443 final public function endAtomic( $fname = __METHOD__ ) { 03444 if ( !$this->mTrxLevel ) { 03445 throw new DBUnexpectedError( $this, 'No atomic transaction is open.' ); 03446 } 03447 if ( $this->mTrxAtomicLevels->isEmpty() || 03448 $this->mTrxAtomicLevels->pop() !== $fname 03449 ) { 03450 throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' ); 03451 } 03452 03453 if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) { 03454 $this->commit( $fname, 'flush' ); 03455 } 03456 } 03457 03473 final public function begin( $fname = __METHOD__ ) { 03474 global $wgDebugDBTransactions; 03475 03476 if ( $this->mTrxLevel ) { // implicit commit 03477 if ( !$this->mTrxAtomicLevels->isEmpty() ) { 03478 // If the current transaction was an automatic atomic one, then we definitely have 03479 // a problem. Same if there is any unclosed atomic level. 03480 throw new DBUnexpectedError( $this, 03481 "Attempted to start explicit transaction when atomic levels are still open." 03482 ); 03483 } elseif ( !$this->mTrxAutomatic ) { 03484 // We want to warn about inadvertently nested begin/commit pairs, but not about 03485 // auto-committing implicit transactions that were started by query() via DBO_TRX 03486 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " . 03487 " performing implicit commit!"; 03488 wfWarn( $msg ); 03489 wfLogDBError( $msg ); 03490 } else { 03491 // if the transaction was automatic and has done write operations, 03492 // log it if $wgDebugDBTransactions is enabled. 03493 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { 03494 wfDebug( "$fname: Automatic transaction with writes in progress" . 03495 " (from {$this->mTrxFname}), performing implicit commit!\n" 03496 ); 03497 } 03498 } 03499 03500 $this->runOnTransactionPreCommitCallbacks(); 03501 $this->doCommit( $fname ); 03502 if ( $this->mTrxDoneWrites ) { 03503 Profiler::instance()->transactionWritingOut( 03504 $this->mServer, $this->mDBname, $this->mTrxShortId ); 03505 } 03506 $this->runOnTransactionIdleCallbacks(); 03507 } 03508 03509 # Avoid fatals if close() was called 03510 if ( !$this->isOpen() ) { 03511 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 03512 } 03513 03514 $this->doBegin( $fname ); 03515 $this->mTrxFname = $fname; 03516 $this->mTrxDoneWrites = false; 03517 $this->mTrxAutomatic = false; 03518 $this->mTrxAutomaticAtomic = false; 03519 $this->mTrxAtomicLevels = new SplStack; 03520 $this->mTrxIdleCallbacks = array(); 03521 $this->mTrxPreCommitCallbacks = array(); 03522 $this->mTrxShortId = wfRandomString( 12 ); 03523 } 03524 03531 protected function doBegin( $fname ) { 03532 $this->query( 'BEGIN', $fname ); 03533 $this->mTrxLevel = 1; 03534 } 03535 03550 final public function commit( $fname = __METHOD__, $flush = '' ) { 03551 if ( !$this->mTrxAtomicLevels->isEmpty() ) { 03552 // There are still atomic sections open. This cannot be ignored 03553 throw new DBUnexpectedError( 03554 $this, 03555 "Attempted to commit transaction while atomic sections are still open" 03556 ); 03557 } 03558 03559 if ( $flush === 'flush' ) { 03560 if ( !$this->mTrxLevel ) { 03561 return; // nothing to do 03562 } elseif ( !$this->mTrxAutomatic ) { 03563 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03564 } 03565 } else { 03566 if ( !$this->mTrxLevel ) { 03567 wfWarn( "$fname: No transaction to commit, something got out of sync!" ); 03568 return; // nothing to do 03569 } elseif ( $this->mTrxAutomatic ) { 03570 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); 03571 } 03572 } 03573 03574 # Avoid fatals if close() was called 03575 if ( !$this->isOpen() ) { 03576 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 03577 } 03578 03579 $this->runOnTransactionPreCommitCallbacks(); 03580 $this->doCommit( $fname ); 03581 if ( $this->mTrxDoneWrites ) { 03582 Profiler::instance()->transactionWritingOut( 03583 $this->mServer, $this->mDBname, $this->mTrxShortId ); 03584 } 03585 $this->runOnTransactionIdleCallbacks(); 03586 } 03587 03594 protected function doCommit( $fname ) { 03595 if ( $this->mTrxLevel ) { 03596 $this->query( 'COMMIT', $fname ); 03597 $this->mTrxLevel = 0; 03598 } 03599 } 03600 03614 final public function rollback( $fname = __METHOD__, $flush = '' ) { 03615 if ( $flush !== 'flush' ) { 03616 if ( !$this->mTrxLevel ) { 03617 wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); 03618 return; // nothing to do 03619 } elseif ( $this->mTrxAutomatic ) { 03620 wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" ); 03621 } 03622 } else { 03623 if ( !$this->mTrxLevel ) { 03624 return; // nothing to do 03625 } elseif ( !$this->mTrxAutomatic ) { 03626 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03627 } 03628 } 03629 03630 # Avoid fatals if close() was called 03631 if ( !$this->isOpen() ) { 03632 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 03633 } 03634 03635 $this->doRollback( $fname ); 03636 $this->mTrxIdleCallbacks = array(); // cancel 03637 $this->mTrxPreCommitCallbacks = array(); // cancel 03638 $this->mTrxAtomicLevels = new SplStack; 03639 if ( $this->mTrxDoneWrites ) { 03640 Profiler::instance()->transactionWritingOut( 03641 $this->mServer, $this->mDBname, $this->mTrxShortId ); 03642 } 03643 } 03644 03651 protected function doRollback( $fname ) { 03652 if ( $this->mTrxLevel ) { 03653 $this->query( 'ROLLBACK', $fname, true ); 03654 $this->mTrxLevel = 0; 03655 } 03656 } 03657 03673 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 03674 $fname = __METHOD__ 03675 ) { 03676 throw new MWException( 03677 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 03678 } 03679 03687 function listTables( $prefix = null, $fname = __METHOD__ ) { 03688 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 03689 } 03690 03695 final public function clearViewsCache() { 03696 $this->allViews = null; 03697 } 03698 03710 public function listViews( $prefix = null, $fname = __METHOD__ ) { 03711 throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' ); 03712 } 03713 03721 public function isView( $name ) { 03722 throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' ); 03723 } 03724 03736 public function timestamp( $ts = 0 ) { 03737 return wfTimestamp( TS_MW, $ts ); 03738 } 03739 03753 public function timestampOrNull( $ts = null ) { 03754 if ( is_null( $ts ) ) { 03755 return null; 03756 } else { 03757 return $this->timestamp( $ts ); 03758 } 03759 } 03760 03775 public function resultObject( $result ) { 03776 if ( empty( $result ) ) { 03777 return false; 03778 } elseif ( $result instanceof ResultWrapper ) { 03779 return $result; 03780 } elseif ( $result === true ) { 03781 // Successful write query 03782 return $result; 03783 } else { 03784 return new ResultWrapper( $this, $result ); 03785 } 03786 } 03787 03793 public function ping() { 03794 # Stub. Not essential to override. 03795 return true; 03796 } 03797 03807 public function getLag() { 03808 return 0; 03809 } 03810 03816 function maxListLen() { 03817 return 0; 03818 } 03819 03829 public function encodeBlob( $b ) { 03830 return $b; 03831 } 03832 03841 public function decodeBlob( $b ) { 03842 return $b; 03843 } 03844 03855 public function setSessionOptions( array $options ) { 03856 } 03857 03874 public function sourceFile( 03875 $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false 03876 ) { 03877 wfSuppressWarnings(); 03878 $fp = fopen( $filename, 'r' ); 03879 wfRestoreWarnings(); 03880 03881 if ( false === $fp ) { 03882 throw new MWException( "Could not open \"{$filename}\".\n" ); 03883 } 03884 03885 if ( !$fname ) { 03886 $fname = __METHOD__ . "( $filename )"; 03887 } 03888 03889 try { 03890 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); 03891 } catch ( MWException $e ) { 03892 fclose( $fp ); 03893 throw $e; 03894 } 03895 03896 fclose( $fp ); 03897 03898 return $error; 03899 } 03900 03909 public function patchPath( $patch ) { 03910 global $IP; 03911 03912 $dbType = $this->getType(); 03913 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 03914 return "$IP/maintenance/$dbType/archives/$patch"; 03915 } else { 03916 return "$IP/maintenance/archives/$patch"; 03917 } 03918 } 03919 03927 public function setSchemaVars( $vars ) { 03928 $this->mSchemaVars = $vars; 03929 } 03930 03944 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 03945 $fname = __METHOD__, $inputCallback = false 03946 ) { 03947 $cmd = ''; 03948 03949 while ( !feof( $fp ) ) { 03950 if ( $lineCallback ) { 03951 call_user_func( $lineCallback ); 03952 } 03953 03954 $line = trim( fgets( $fp ) ); 03955 03956 if ( $line == '' ) { 03957 continue; 03958 } 03959 03960 if ( '-' == $line[0] && '-' == $line[1] ) { 03961 continue; 03962 } 03963 03964 if ( $cmd != '' ) { 03965 $cmd .= ' '; 03966 } 03967 03968 $done = $this->streamStatementEnd( $cmd, $line ); 03969 03970 $cmd .= "$line\n"; 03971 03972 if ( $done || feof( $fp ) ) { 03973 $cmd = $this->replaceVars( $cmd ); 03974 03975 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { 03976 $res = $this->query( $cmd, $fname ); 03977 03978 if ( $resultCallback ) { 03979 call_user_func( $resultCallback, $res, $this ); 03980 } 03981 03982 if ( false === $res ) { 03983 $err = $this->lastError(); 03984 03985 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 03986 } 03987 } 03988 $cmd = ''; 03989 } 03990 } 03991 03992 return true; 03993 } 03994 04002 public function streamStatementEnd( &$sql, &$newLine ) { 04003 if ( $this->delimiter ) { 04004 $prev = $newLine; 04005 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 04006 if ( $newLine != $prev ) { 04007 return true; 04008 } 04009 } 04010 04011 return false; 04012 } 04013 04031 protected function replaceSchemaVars( $ins ) { 04032 $vars = $this->getSchemaVars(); 04033 foreach ( $vars as $var => $value ) { 04034 // replace '{$var}' 04035 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 04036 // replace `{$var}` 04037 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 04038 // replace /*$var*/ 04039 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); 04040 } 04041 04042 return $ins; 04043 } 04044 04051 protected function replaceVars( $ins ) { 04052 $ins = $this->replaceSchemaVars( $ins ); 04053 04054 // Table prefixes 04055 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 04056 array( $this, 'tableNameCallback' ), $ins ); 04057 04058 // Index names 04059 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 04060 array( $this, 'indexNameCallback' ), $ins ); 04061 04062 return $ins; 04063 } 04064 04071 protected function getSchemaVars() { 04072 if ( $this->mSchemaVars ) { 04073 return $this->mSchemaVars; 04074 } else { 04075 return $this->getDefaultSchemaVars(); 04076 } 04077 } 04078 04087 protected function getDefaultSchemaVars() { 04088 return array(); 04089 } 04090 04097 protected function tableNameCallback( $matches ) { 04098 return $this->tableName( $matches[1] ); 04099 } 04100 04107 protected function indexNameCallback( $matches ) { 04108 return $this->indexName( $matches[1] ); 04109 } 04110 04119 public function lockIsFree( $lockName, $method ) { 04120 return true; 04121 } 04122 04134 public function lock( $lockName, $method, $timeout = 5 ) { 04135 return true; 04136 } 04137 04148 public function unlock( $lockName, $method ) { 04149 return true; 04150 } 04151 04161 public function lockTables( $read, $write, $method, $lowPriority = true ) { 04162 return true; 04163 } 04164 04171 public function unlockTables( $method ) { 04172 return true; 04173 } 04174 04182 public function dropTable( $tableName, $fName = __METHOD__ ) { 04183 if ( !$this->tableExists( $tableName, $fName ) ) { 04184 return false; 04185 } 04186 $sql = "DROP TABLE " . $this->tableName( $tableName ); 04187 if ( $this->cascadingDeletes() ) { 04188 $sql .= " CASCADE"; 04189 } 04190 04191 return $this->query( $sql, $fName ); 04192 } 04193 04200 public function getSearchEngine() { 04201 return 'SearchEngineDummy'; 04202 } 04203 04211 public function getInfinity() { 04212 return 'infinity'; 04213 } 04214 04221 public function encodeExpiry( $expiry ) { 04222 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 04223 ? $this->getInfinity() 04224 : $this->timestamp( $expiry ); 04225 } 04226 04234 public function decodeExpiry( $expiry, $format = TS_MW ) { 04235 return ( $expiry == '' || $expiry == $this->getInfinity() ) 04236 ? 'infinity' 04237 : wfTimestamp( $format, $expiry ); 04238 } 04239 04249 public function setBigSelects( $value = true ) { 04250 // no-op 04251 } 04252 04257 public function __toString() { 04258 return (string)$this->mConn; 04259 } 04260 04264 public function __destruct() { 04265 if ( $this->mTrxLevel && $this->mTrxDoneWrites ) { 04266 trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." ); 04267 } 04268 if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) { 04269 $callers = array(); 04270 foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) { 04271 $callers[] = $callbackInfo[1]; 04272 } 04273 $callers = implode( ', ', $callers ); 04274 trigger_error( "DB transaction callbacks still pending (from $callers)." ); 04275 } 04276 } 04277 }