MediaWiki
REL1_23
|
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 00278 private $mTrxFname = null; 00279 00286 private $mTrxDoneWrites = false; 00287 00294 private $mTrxAutomatic = false; 00295 00301 private $mTrxAtomicLevels; 00302 00308 private $mTrxAutomaticAtomic = false; 00309 00314 protected $fileHandle = null; 00315 00320 protected $allViews = null; 00321 00322 # ------------------------------------------------------------------------------ 00323 # Accessors 00324 # ------------------------------------------------------------------------------ 00325 # These optionally set a variable and return the previous state 00326 00334 public function getServerInfo() { 00335 return $this->getServerVersion(); 00336 } 00337 00341 public function getDelimiter() { 00342 return $this->delimiter; 00343 } 00344 00354 public function debug( $debug = null ) { 00355 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 00356 } 00357 00379 public function bufferResults( $buffer = null ) { 00380 if ( is_null( $buffer ) ) { 00381 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 00382 } else { 00383 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 00384 } 00385 } 00386 00399 public function ignoreErrors( $ignoreErrors = null ) { 00400 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 00401 } 00402 00411 public function trxLevel() { 00412 return $this->mTrxLevel; 00413 } 00414 00420 public function errorCount( $count = null ) { 00421 return wfSetVar( $this->mErrorCount, $count ); 00422 } 00423 00429 public function tablePrefix( $prefix = null ) { 00430 return wfSetVar( $this->mTablePrefix, $prefix ); 00431 } 00432 00438 public function dbSchema( $schema = null ) { 00439 return wfSetVar( $this->mSchema, $schema ); 00440 } 00441 00447 public function setFileHandle( $fh ) { 00448 $this->fileHandle = $fh; 00449 } 00450 00460 public function getLBInfo( $name = null ) { 00461 if ( is_null( $name ) ) { 00462 return $this->mLBInfo; 00463 } else { 00464 if ( array_key_exists( $name, $this->mLBInfo ) ) { 00465 return $this->mLBInfo[$name]; 00466 } else { 00467 return null; 00468 } 00469 } 00470 } 00471 00480 public function setLBInfo( $name, $value = null ) { 00481 if ( is_null( $value ) ) { 00482 $this->mLBInfo = $name; 00483 } else { 00484 $this->mLBInfo[$name] = $value; 00485 } 00486 } 00487 00495 public function setFakeSlaveLag( $lag ) { 00496 } 00497 00503 public function setFakeMaster( $enabled = true ) { 00504 } 00505 00511 public function cascadingDeletes() { 00512 return false; 00513 } 00514 00520 public function cleanupTriggers() { 00521 return false; 00522 } 00523 00530 public function strictIPs() { 00531 return false; 00532 } 00533 00539 public function realTimestamps() { 00540 return false; 00541 } 00542 00548 public function implicitGroupby() { 00549 return true; 00550 } 00551 00558 public function implicitOrderby() { 00559 return true; 00560 } 00561 00568 public function searchableIPs() { 00569 return false; 00570 } 00571 00577 public function functionalIndexes() { 00578 return false; 00579 } 00580 00585 public function lastQuery() { 00586 return $this->mLastQuery; 00587 } 00588 00595 public function doneWrites() { 00596 return $this->mDoneWrites; 00597 } 00598 00605 public function writesOrCallbacksPending() { 00606 return $this->mTrxLevel && ( 00607 $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks 00608 ); 00609 } 00610 00615 public function isOpen() { 00616 return $this->mOpened; 00617 } 00618 00630 public function setFlag( $flag ) { 00631 global $wgDebugDBTransactions; 00632 $this->mFlags |= $flag; 00633 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00634 wfDebug( "Implicit transactions are now enabled.\n" ); 00635 } 00636 } 00637 00649 public function clearFlag( $flag ) { 00650 global $wgDebugDBTransactions; 00651 $this->mFlags &= ~$flag; 00652 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00653 wfDebug( "Implicit transactions are now disabled.\n" ); 00654 } 00655 } 00656 00669 public function getFlag( $flag ) { 00670 return !!( $this->mFlags & $flag ); 00671 } 00672 00679 public function getProperty( $name ) { 00680 return $this->$name; 00681 } 00682 00686 public function getWikiID() { 00687 if ( $this->mTablePrefix ) { 00688 return "{$this->mDBname}-{$this->mTablePrefix}"; 00689 } else { 00690 return $this->mDBname; 00691 } 00692 } 00693 00699 public function getSchemaPath() { 00700 global $IP; 00701 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) { 00702 return "$IP/maintenance/" . $this->getType() . "/tables.sql"; 00703 } else { 00704 return "$IP/maintenance/tables.sql"; 00705 } 00706 } 00707 00708 # ------------------------------------------------------------------------------ 00709 # Other functions 00710 # ------------------------------------------------------------------------------ 00711 00724 function __construct( $params = null ) { 00725 global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions; 00726 00727 $this->mTrxAtomicLevels = new SplStack; 00728 00729 if ( is_array( $params ) ) { // MW 1.22 00730 $server = $params['host']; 00731 $user = $params['user']; 00732 $password = $params['password']; 00733 $dbName = $params['dbname']; 00734 $flags = $params['flags']; 00735 $tablePrefix = $params['tablePrefix']; 00736 $schema = $params['schema']; 00737 $foreign = $params['foreign']; 00738 } else { // legacy calling pattern 00739 wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" ); 00740 $args = func_get_args(); 00741 $server = isset( $args[0] ) ? $args[0] : false; 00742 $user = isset( $args[1] ) ? $args[1] : false; 00743 $password = isset( $args[2] ) ? $args[2] : false; 00744 $dbName = isset( $args[3] ) ? $args[3] : false; 00745 $flags = isset( $args[4] ) ? $args[4] : 0; 00746 $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global'; 00747 $schema = 'get from global'; 00748 $foreign = isset( $args[6] ) ? $args[6] : false; 00749 } 00750 00751 $this->mFlags = $flags; 00752 if ( $this->mFlags & DBO_DEFAULT ) { 00753 if ( $wgCommandLineMode ) { 00754 $this->mFlags &= ~DBO_TRX; 00755 if ( $wgDebugDBTransactions ) { 00756 wfDebug( "Implicit transaction open disabled.\n" ); 00757 } 00758 } else { 00759 $this->mFlags |= DBO_TRX; 00760 if ( $wgDebugDBTransactions ) { 00761 wfDebug( "Implicit transaction open enabled.\n" ); 00762 } 00763 } 00764 } 00765 00767 if ( $tablePrefix == 'get from global' ) { 00768 $this->mTablePrefix = $wgDBprefix; 00769 } else { 00770 $this->mTablePrefix = $tablePrefix; 00771 } 00772 00774 if ( $schema == 'get from global' ) { 00775 $this->mSchema = $wgDBmwschema; 00776 } else { 00777 $this->mSchema = $schema; 00778 } 00779 00780 $this->mForeign = $foreign; 00781 00782 if ( $user ) { 00783 $this->open( $server, $user, $password, $dbName ); 00784 } 00785 } 00786 00792 public function __sleep() { 00793 throw new MWException( 'Database serialization may cause problems, since ' . 00794 'the connection is not restored on wakeup.' ); 00795 } 00796 00819 final public static function factory( $dbType, $p = array() ) { 00820 $canonicalDBTypes = array( 00821 'mysql' => array( 'mysqli', 'mysql' ), 00822 'postgres' => array(), 00823 'sqlite' => array(), 00824 'oracle' => array(), 00825 'mssql' => array(), 00826 ); 00827 00828 $driver = false; 00829 $dbType = strtolower( $dbType ); 00830 if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) { 00831 $possibleDrivers = $canonicalDBTypes[$dbType]; 00832 if ( !empty( $p['driver'] ) ) { 00833 if ( in_array( $p['driver'], $possibleDrivers ) ) { 00834 $driver = $p['driver']; 00835 } else { 00836 throw new MWException( __METHOD__ . 00837 " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" ); 00838 } 00839 } else { 00840 foreach ( $possibleDrivers as $posDriver ) { 00841 if ( extension_loaded( $posDriver ) ) { 00842 $driver = $posDriver; 00843 break; 00844 } 00845 } 00846 } 00847 } else { 00848 $driver = $dbType; 00849 } 00850 if ( $driver === false ) { 00851 throw new MWException( __METHOD__ . 00852 " no viable database extension found for type '$dbType'" ); 00853 } 00854 00855 // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema, 00856 // and everything else doesn't use a schema (e.g. null) 00857 // Although postgres and oracle support schemas, we don't use them (yet) 00858 // to maintain backwards compatibility 00859 $defaultSchemas = array( 00860 'mysql' => null, 00861 'postgres' => null, 00862 'sqlite' => null, 00863 'oracle' => null, 00864 'mssql' => 'get from global', 00865 ); 00866 00867 $class = 'Database' . ucfirst( $driver ); 00868 if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) { 00869 $params = array( 00870 'host' => isset( $p['host'] ) ? $p['host'] : false, 00871 'user' => isset( $p['user'] ) ? $p['user'] : false, 00872 'password' => isset( $p['password'] ) ? $p['password'] : false, 00873 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false, 00874 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0, 00875 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global', 00876 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType], 00877 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false 00878 ); 00879 00880 return new $class( $params ); 00881 } else { 00882 return null; 00883 } 00884 } 00885 00886 protected function installErrorHandler() { 00887 $this->mPHPError = false; 00888 $this->htmlErrors = ini_set( 'html_errors', '0' ); 00889 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 00890 } 00891 00895 protected function restoreErrorHandler() { 00896 restore_error_handler(); 00897 if ( $this->htmlErrors !== false ) { 00898 ini_set( 'html_errors', $this->htmlErrors ); 00899 } 00900 if ( $this->mPHPError ) { 00901 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 00902 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 00903 00904 return $error; 00905 } else { 00906 return false; 00907 } 00908 } 00909 00914 public function connectionErrorHandler( $errno, $errstr ) { 00915 $this->mPHPError = $errstr; 00916 } 00917 00925 public function close() { 00926 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 00927 throw new MWException( "Transaction idle callbacks still pending." ); 00928 } 00929 $this->mOpened = false; 00930 if ( $this->mConn ) { 00931 if ( $this->trxLevel() ) { 00932 if ( !$this->mTrxAutomatic ) { 00933 wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . 00934 " performing implicit commit before closing connection!" ); 00935 } 00936 00937 $this->commit( __METHOD__, 'flush' ); 00938 } 00939 00940 $ret = $this->closeConnection(); 00941 $this->mConn = false; 00942 00943 return $ret; 00944 } else { 00945 return true; 00946 } 00947 } 00948 00954 abstract protected function closeConnection(); 00955 00960 function reportConnectionError( $error = 'Unknown error' ) { 00961 $myError = $this->lastError(); 00962 if ( $myError ) { 00963 $error = $myError; 00964 } 00965 00966 # New method 00967 throw new DBConnectionError( $this, $error ); 00968 } 00969 00977 abstract protected function doQuery( $sql ); 00978 00986 public function isWriteQuery( $sql ) { 00987 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 00988 } 00989 01012 public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { 01013 global $wgUser, $wgDebugDBTransactions; 01014 01015 $this->mLastQuery = $sql; 01016 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { 01017 # Set a flag indicating that writes have been done 01018 wfDebug( __METHOD__ . ": Writes done: $sql\n" ); 01019 $this->mDoneWrites = true; 01020 } 01021 01022 # Add a comment for easy SHOW PROCESSLIST interpretation 01023 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 01024 $userName = $wgUser->getName(); 01025 if ( mb_strlen( $userName ) > 15 ) { 01026 $userName = mb_substr( $userName, 0, 15 ) . '...'; 01027 } 01028 $userName = str_replace( '/', '', $userName ); 01029 } else { 01030 $userName = ''; 01031 } 01032 01033 // Add trace comment to the begin of the sql string, right after the operator. 01034 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) 01035 $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 ); 01036 01037 # If DBO_TRX is set, start a transaction 01038 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && 01039 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' 01040 ) { 01041 # Avoid establishing transactions for SHOW and SET statements too - 01042 # that would delay transaction initializations to once connection 01043 # is really used by application 01044 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 01045 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 01046 if ( $wgDebugDBTransactions ) { 01047 wfDebug( "Implicit transaction start.\n" ); 01048 } 01049 $this->begin( __METHOD__ . " ($fname)" ); 01050 $this->mTrxAutomatic = true; 01051 } 01052 } 01053 01054 # Keep track of whether the transaction has write queries pending 01055 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { 01056 $this->mTrxDoneWrites = true; 01057 Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname ); 01058 } 01059 01060 $queryProf = ''; 01061 $totalProf = ''; 01062 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 01063 01064 if ( !Profiler::instance()->isStub() ) { 01065 # generalizeSQL will probably cut down the query to reasonable 01066 # logging size most of the time. The substr is really just a sanity check. 01067 if ( $isMaster ) { 01068 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 01069 $totalProf = 'DatabaseBase::query-master'; 01070 } else { 01071 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 01072 $totalProf = 'DatabaseBase::query'; 01073 } 01074 wfProfileIn( $totalProf ); 01075 wfProfileIn( $queryProf ); 01076 } 01077 01078 if ( $this->debug() ) { 01079 static $cnt = 0; 01080 01081 $cnt++; 01082 $sqlx = substr( $commentedSql, 0, 500 ); 01083 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 01084 01085 $master = $isMaster ? 'master' : 'slave'; 01086 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 01087 } 01088 01089 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 01090 01091 # Do the query and handle errors 01092 $ret = $this->doQuery( $commentedSql ); 01093 01094 MWDebug::queryTime( $queryId ); 01095 01096 # Try reconnecting if the connection was lost 01097 if ( false === $ret && $this->wasErrorReissuable() ) { 01098 # Transaction is gone, like it or not 01099 $hadTrx = $this->mTrxLevel; // possible lost transaction 01100 $this->mTrxLevel = 0; 01101 wfDebug( "Connection lost, reconnecting...\n" ); 01102 01103 if ( $this->ping() ) { 01104 wfDebug( "Reconnected\n" ); 01105 $sqlx = substr( $commentedSql, 0, 500 ); 01106 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 01107 global $wgRequestTime; 01108 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 01109 if ( $elapsed < 300 ) { 01110 # Not a database error to lose a transaction after a minute or two 01111 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" ); 01112 } 01113 if ( !$hadTrx ) { 01114 # Should be safe to silently retry 01115 $ret = $this->doQuery( $commentedSql ); 01116 } 01117 } else { 01118 wfDebug( "Failed\n" ); 01119 } 01120 } 01121 01122 if ( false === $ret ) { 01123 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 01124 } 01125 01126 if ( !Profiler::instance()->isStub() ) { 01127 wfProfileOut( $queryProf ); 01128 wfProfileOut( $totalProf ); 01129 } 01130 01131 return $this->resultObject( $ret ); 01132 } 01133 01145 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 01146 # Ignore errors during error handling to avoid infinite recursion 01147 $ignore = $this->ignoreErrors( true ); 01148 ++$this->mErrorCount; 01149 01150 if ( $ignore || $tempIgnore ) { 01151 wfDebug( "SQL ERROR (ignored): $error\n" ); 01152 $this->ignoreErrors( $ignore ); 01153 } else { 01154 $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ); 01155 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" ); 01156 wfDebug( "SQL ERROR: " . $error . "\n" ); 01157 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 01158 } 01159 } 01160 01175 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 01176 /* MySQL doesn't support prepared statements (yet), so just 01177 * pack up the query for reference. We'll manually replace 01178 * the bits later. 01179 */ 01180 return array( 'query' => $sql, 'func' => $func ); 01181 } 01182 01187 protected function freePrepared( $prepared ) { 01188 /* No-op by default */ 01189 } 01190 01198 public function execute( $prepared, $args = null ) { 01199 if ( !is_array( $args ) ) { 01200 # Pull the var args 01201 $args = func_get_args(); 01202 array_shift( $args ); 01203 } 01204 01205 $sql = $this->fillPrepared( $prepared['query'], $args ); 01206 01207 return $this->query( $sql, $prepared['func'] ); 01208 } 01209 01217 public function fillPrepared( $preparedQuery, $args ) { 01218 reset( $args ); 01219 $this->preparedArgs =& $args; 01220 01221 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 01222 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 01223 } 01224 01234 protected function fillPreparedArg( $matches ) { 01235 switch ( $matches[1] ) { 01236 case '\\?': 01237 return '?'; 01238 case '\\!': 01239 return '!'; 01240 case '\\&': 01241 return '&'; 01242 } 01243 01244 list( /* $n */, $arg ) = each( $this->preparedArgs ); 01245 01246 switch ( $matches[1] ) { 01247 case '?': 01248 return $this->addQuotes( $arg ); 01249 case '!': 01250 return $arg; 01251 case '&': 01252 # return $this->addQuotes( file_get_contents( $arg ) ); 01253 throw new DBUnexpectedError( 01254 $this, 01255 '& mode is not implemented. If it\'s really needed, uncomment the line above.' 01256 ); 01257 default: 01258 throw new DBUnexpectedError( 01259 $this, 01260 'Received invalid match. This should never happen!' 01261 ); 01262 } 01263 } 01264 01272 public function freeResult( $res ) { 01273 } 01274 01292 public function selectField( $table, $var, $cond = '', $fname = __METHOD__, 01293 $options = array() 01294 ) { 01295 if ( !is_array( $options ) ) { 01296 $options = array( $options ); 01297 } 01298 01299 $options['LIMIT'] = 1; 01300 01301 $res = $this->select( $table, $var, $cond, $fname, $options ); 01302 01303 if ( $res === false || !$this->numRows( $res ) ) { 01304 return false; 01305 } 01306 01307 $row = $this->fetchRow( $res ); 01308 01309 if ( $row !== false ) { 01310 return reset( $row ); 01311 } else { 01312 return false; 01313 } 01314 } 01315 01325 public function makeSelectOptions( $options ) { 01326 $preLimitTail = $postLimitTail = ''; 01327 $startOpts = ''; 01328 01329 $noKeyOptions = array(); 01330 01331 foreach ( $options as $key => $option ) { 01332 if ( is_numeric( $key ) ) { 01333 $noKeyOptions[$option] = true; 01334 } 01335 } 01336 01337 $preLimitTail .= $this->makeGroupByWithHaving( $options ); 01338 01339 $preLimitTail .= $this->makeOrderBy( $options ); 01340 01341 // if (isset($options['LIMIT'])) { 01342 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 01343 // isset($options['OFFSET']) ? $options['OFFSET'] 01344 // : false); 01345 // } 01346 01347 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 01348 $postLimitTail .= ' FOR UPDATE'; 01349 } 01350 01351 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 01352 $postLimitTail .= ' LOCK IN SHARE MODE'; 01353 } 01354 01355 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01356 $startOpts .= 'DISTINCT'; 01357 } 01358 01359 # Various MySQL extensions 01360 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 01361 $startOpts .= ' /*! STRAIGHT_JOIN */'; 01362 } 01363 01364 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 01365 $startOpts .= ' HIGH_PRIORITY'; 01366 } 01367 01368 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 01369 $startOpts .= ' SQL_BIG_RESULT'; 01370 } 01371 01372 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 01373 $startOpts .= ' SQL_BUFFER_RESULT'; 01374 } 01375 01376 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 01377 $startOpts .= ' SQL_SMALL_RESULT'; 01378 } 01379 01380 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 01381 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 01382 } 01383 01384 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 01385 $startOpts .= ' SQL_CACHE'; 01386 } 01387 01388 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 01389 $startOpts .= ' SQL_NO_CACHE'; 01390 } 01391 01392 if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) { 01393 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01394 } else { 01395 $useIndex = ''; 01396 } 01397 01398 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01399 } 01400 01409 public function makeGroupByWithHaving( $options ) { 01410 $sql = ''; 01411 if ( isset( $options['GROUP BY'] ) ) { 01412 $gb = is_array( $options['GROUP BY'] ) 01413 ? implode( ',', $options['GROUP BY'] ) 01414 : $options['GROUP BY']; 01415 $sql .= ' GROUP BY ' . $gb; 01416 } 01417 if ( isset( $options['HAVING'] ) ) { 01418 $having = is_array( $options['HAVING'] ) 01419 ? $this->makeList( $options['HAVING'], LIST_AND ) 01420 : $options['HAVING']; 01421 $sql .= ' HAVING ' . $having; 01422 } 01423 01424 return $sql; 01425 } 01426 01435 public function makeOrderBy( $options ) { 01436 if ( isset( $options['ORDER BY'] ) ) { 01437 $ob = is_array( $options['ORDER BY'] ) 01438 ? implode( ',', $options['ORDER BY'] ) 01439 : $options['ORDER BY']; 01440 01441 return ' ORDER BY ' . $ob; 01442 } 01443 01444 return ''; 01445 } 01446 01586 public function select( $table, $vars, $conds = '', $fname = __METHOD__, 01587 $options = array(), $join_conds = array() ) { 01588 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 01589 01590 return $this->query( $sql, $fname ); 01591 } 01592 01609 public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, 01610 $options = array(), $join_conds = array() 01611 ) { 01612 if ( is_array( $vars ) ) { 01613 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 01614 } 01615 01616 $options = (array)$options; 01617 $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 01618 ? $options['USE INDEX'] 01619 : array(); 01620 01621 if ( is_array( $table ) ) { 01622 $from = ' FROM ' . 01623 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds ); 01624 } elseif ( $table != '' ) { 01625 if ( $table[0] == ' ' ) { 01626 $from = ' FROM ' . $table; 01627 } else { 01628 $from = ' FROM ' . 01629 $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() ); 01630 } 01631 } else { 01632 $from = ''; 01633 } 01634 01635 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = 01636 $this->makeSelectOptions( $options ); 01637 01638 if ( !empty( $conds ) ) { 01639 if ( is_array( $conds ) ) { 01640 $conds = $this->makeList( $conds, LIST_AND ); 01641 } 01642 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 01643 } else { 01644 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 01645 } 01646 01647 if ( isset( $options['LIMIT'] ) ) { 01648 $sql = $this->limitResult( $sql, $options['LIMIT'], 01649 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 01650 } 01651 $sql = "$sql $postLimitTail"; 01652 01653 if ( isset( $options['EXPLAIN'] ) ) { 01654 $sql = 'EXPLAIN ' . $sql; 01655 } 01656 01657 return $sql; 01658 } 01659 01674 public function selectRow( $table, $vars, $conds, $fname = __METHOD__, 01675 $options = array(), $join_conds = array() 01676 ) { 01677 $options = (array)$options; 01678 $options['LIMIT'] = 1; 01679 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 01680 01681 if ( $res === false ) { 01682 return false; 01683 } 01684 01685 if ( !$this->numRows( $res ) ) { 01686 return false; 01687 } 01688 01689 $obj = $this->fetchObject( $res ); 01690 01691 return $obj; 01692 } 01693 01714 public function estimateRowCount( $table, $vars = '*', $conds = '', 01715 $fname = __METHOD__, $options = array() 01716 ) { 01717 $rows = 0; 01718 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 01719 01720 if ( $res ) { 01721 $row = $this->fetchRow( $res ); 01722 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01723 } 01724 01725 return $rows; 01726 } 01727 01736 static function generalizeSQL( $sql ) { 01737 # This does the same as the regexp below would do, but in such a way 01738 # as to avoid crashing php on some large strings. 01739 # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql ); 01740 01741 $sql = str_replace( "\\\\", '', $sql ); 01742 $sql = str_replace( "\\'", '', $sql ); 01743 $sql = str_replace( "\\\"", '', $sql ); 01744 $sql = preg_replace( "/'.*'/s", "'X'", $sql ); 01745 $sql = preg_replace( '/".*"/s', "'X'", $sql ); 01746 01747 # All newlines, tabs, etc replaced by single space 01748 $sql = preg_replace( '/\s+/', ' ', $sql ); 01749 01750 # All numbers => N 01751 $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql ); 01752 $sql = preg_replace( '/-?\d+/s', 'N', $sql ); 01753 01754 return $sql; 01755 } 01756 01765 public function fieldExists( $table, $field, $fname = __METHOD__ ) { 01766 $info = $this->fieldInfo( $table, $field ); 01767 01768 return (bool)$info; 01769 } 01770 01781 public function indexExists( $table, $index, $fname = __METHOD__ ) { 01782 if ( !$this->tableExists( $table ) ) { 01783 return null; 01784 } 01785 01786 $info = $this->indexInfo( $table, $index, $fname ); 01787 if ( is_null( $info ) ) { 01788 return null; 01789 } else { 01790 return $info !== false; 01791 } 01792 } 01793 01801 public function tableExists( $table, $fname = __METHOD__ ) { 01802 $table = $this->tableName( $table ); 01803 $old = $this->ignoreErrors( true ); 01804 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 01805 $this->ignoreErrors( $old ); 01806 01807 return (bool)$res; 01808 } 01809 01818 public function indexUnique( $table, $index ) { 01819 $indexInfo = $this->indexInfo( $table, $index ); 01820 01821 if ( !$indexInfo ) { 01822 return null; 01823 } 01824 01825 return !$indexInfo[0]->Non_unique; 01826 } 01827 01834 protected function makeInsertOptions( $options ) { 01835 return implode( ' ', $options ); 01836 } 01837 01871 public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { 01872 # No rows to insert, easy just return now 01873 if ( !count( $a ) ) { 01874 return true; 01875 } 01876 01877 $table = $this->tableName( $table ); 01878 01879 if ( !is_array( $options ) ) { 01880 $options = array( $options ); 01881 } 01882 01883 $fh = null; 01884 if ( isset( $options['fileHandle'] ) ) { 01885 $fh = $options['fileHandle']; 01886 } 01887 $options = $this->makeInsertOptions( $options ); 01888 01889 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 01890 $multi = true; 01891 $keys = array_keys( $a[0] ); 01892 } else { 01893 $multi = false; 01894 $keys = array_keys( $a ); 01895 } 01896 01897 $sql = 'INSERT ' . $options . 01898 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 01899 01900 if ( $multi ) { 01901 $first = true; 01902 foreach ( $a as $row ) { 01903 if ( $first ) { 01904 $first = false; 01905 } else { 01906 $sql .= ','; 01907 } 01908 $sql .= '(' . $this->makeList( $row ) . ')'; 01909 } 01910 } else { 01911 $sql .= '(' . $this->makeList( $a ) . ')'; 01912 } 01913 01914 if ( $fh !== null && false === fwrite( $fh, $sql ) ) { 01915 return false; 01916 } elseif ( $fh !== null ) { 01917 return true; 01918 } 01919 01920 return (bool)$this->query( $sql, $fname ); 01921 } 01922 01929 protected function makeUpdateOptionsArray( $options ) { 01930 if ( !is_array( $options ) ) { 01931 $options = array( $options ); 01932 } 01933 01934 $opts = array(); 01935 01936 if ( in_array( 'LOW_PRIORITY', $options ) ) { 01937 $opts[] = $this->lowPriorityOption(); 01938 } 01939 01940 if ( in_array( 'IGNORE', $options ) ) { 01941 $opts[] = 'IGNORE'; 01942 } 01943 01944 return $opts; 01945 } 01946 01953 protected function makeUpdateOptions( $options ) { 01954 $opts = $this->makeUpdateOptionsArray( $options ); 01955 01956 return implode( ' ', $opts ); 01957 } 01958 01977 function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) { 01978 $table = $this->tableName( $table ); 01979 $opts = $this->makeUpdateOptions( $options ); 01980 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 01981 01982 if ( $conds !== array() && $conds !== '*' ) { 01983 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 01984 } 01985 01986 return $this->query( $sql, $fname ); 01987 } 01988 02003 public function makeList( $a, $mode = LIST_COMMA ) { 02004 if ( !is_array( $a ) ) { 02005 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 02006 } 02007 02008 $first = true; 02009 $list = ''; 02010 02011 foreach ( $a as $field => $value ) { 02012 if ( !$first ) { 02013 if ( $mode == LIST_AND ) { 02014 $list .= ' AND '; 02015 } elseif ( $mode == LIST_OR ) { 02016 $list .= ' OR '; 02017 } else { 02018 $list .= ','; 02019 } 02020 } else { 02021 $first = false; 02022 } 02023 02024 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 02025 $list .= "($value)"; 02026 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 02027 $list .= "$value"; 02028 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 02029 if ( count( $value ) == 0 ) { 02030 throw new MWException( __METHOD__ . ": empty input for field $field" ); 02031 } elseif ( count( $value ) == 1 ) { 02032 // Special-case single values, as IN isn't terribly efficient 02033 // Don't necessarily assume the single key is 0; we don't 02034 // enforce linear numeric ordering on other arrays here. 02035 $value = array_values( $value ); 02036 $list .= $field . " = " . $this->addQuotes( $value[0] ); 02037 } else { 02038 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 02039 } 02040 } elseif ( $value === null ) { 02041 if ( $mode == LIST_AND || $mode == LIST_OR ) { 02042 $list .= "$field IS "; 02043 } elseif ( $mode == LIST_SET ) { 02044 $list .= "$field = "; 02045 } 02046 $list .= 'NULL'; 02047 } else { 02048 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 02049 $list .= "$field = "; 02050 } 02051 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 02052 } 02053 } 02054 02055 return $list; 02056 } 02057 02068 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 02069 $conds = array(); 02070 02071 foreach ( $data as $base => $sub ) { 02072 if ( count( $sub ) ) { 02073 $conds[] = $this->makeList( 02074 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 02075 LIST_AND ); 02076 } 02077 } 02078 02079 if ( $conds ) { 02080 return $this->makeList( $conds, LIST_OR ); 02081 } else { 02082 // Nothing to search for... 02083 return false; 02084 } 02085 } 02086 02095 public function aggregateValue( $valuedata, $valuename = 'value' ) { 02096 return $valuename; 02097 } 02098 02103 public function bitNot( $field ) { 02104 return "(~$field)"; 02105 } 02106 02112 public function bitAnd( $fieldLeft, $fieldRight ) { 02113 return "($fieldLeft & $fieldRight)"; 02114 } 02115 02121 public function bitOr( $fieldLeft, $fieldRight ) { 02122 return "($fieldLeft | $fieldRight)"; 02123 } 02124 02131 public function buildConcat( $stringList ) { 02132 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 02133 } 02134 02151 public function buildGroupConcatField( 02152 $delim, $table, $field, $conds = '', $join_conds = array() 02153 ) { 02154 $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')'; 02155 02156 return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')'; 02157 } 02158 02168 public function selectDB( $db ) { 02169 # Stub. Shouldn't cause serious problems if it's not overridden, but 02170 # if your database engine supports a concept similar to MySQL's 02171 # databases you may as well. 02172 $this->mDBname = $db; 02173 02174 return true; 02175 } 02176 02180 public function getDBname() { 02181 return $this->mDBname; 02182 } 02183 02187 public function getServer() { 02188 return $this->mServer; 02189 } 02190 02208 public function tableName( $name, $format = 'quoted' ) { 02209 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema; 02210 # Skip the entire process when we have a string quoted on both ends. 02211 # Note that we check the end so that we will still quote any use of 02212 # use of `database`.table. But won't break things if someone wants 02213 # to query a database table with a dot in the name. 02214 if ( $this->isQuotedIdentifier( $name ) ) { 02215 return $name; 02216 } 02217 02218 # Lets test for any bits of text that should never show up in a table 02219 # name. Basically anything like JOIN or ON which are actually part of 02220 # SQL queries, but may end up inside of the table value to combine 02221 # sql. Such as how the API is doing. 02222 # Note that we use a whitespace test rather than a \b test to avoid 02223 # any remote case where a word like on may be inside of a table name 02224 # surrounded by symbols which may be considered word breaks. 02225 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 02226 return $name; 02227 } 02228 02229 # Split database and table into proper variables. 02230 # We reverse the explode so that database.table and table both output 02231 # the correct table. 02232 $dbDetails = explode( '.', $name, 2 ); 02233 if ( count( $dbDetails ) == 3 ) { 02234 list( $database, $schema, $table ) = $dbDetails; 02235 # We don't want any prefix added in this case 02236 $prefix = ''; 02237 } elseif ( count( $dbDetails ) == 2 ) { 02238 list( $database, $table ) = $dbDetails; 02239 # We don't want any prefix added in this case 02240 # In dbs that support it, $database may actually be the schema 02241 # but that doesn't affect any of the functionality here 02242 $prefix = ''; 02243 $schema = null; 02244 } else { 02245 list( $table ) = $dbDetails; 02246 if ( $wgSharedDB !== null # We have a shared database 02247 && $this->mForeign == false # We're not working on a foreign database 02248 && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`' 02249 && in_array( $table, $wgSharedTables ) # A shared table is selected 02250 ) { 02251 $database = $wgSharedDB; 02252 $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema; 02253 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; 02254 } else { 02255 $database = null; 02256 $schema = $this->mSchema; # Default schema 02257 $prefix = $this->mTablePrefix; # Default prefix 02258 } 02259 } 02260 02261 # Quote $table and apply the prefix if not quoted. 02262 # $tableName might be empty if this is called from Database::replaceVars() 02263 $tableName = "{$prefix}{$table}"; 02264 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) { 02265 $tableName = $this->addIdentifierQuotes( $tableName ); 02266 } 02267 02268 # Quote $schema and merge it with the table name if needed 02269 if ( $schema !== null ) { 02270 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) { 02271 $schema = $this->addIdentifierQuotes( $schema ); 02272 } 02273 $tableName = $schema . '.' . $tableName; 02274 } 02275 02276 # Quote $database and merge it with the table name if needed 02277 if ( $database !== null ) { 02278 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 02279 $database = $this->addIdentifierQuotes( $database ); 02280 } 02281 $tableName = $database . '.' . $tableName; 02282 } 02283 02284 return $tableName; 02285 } 02286 02298 public function tableNames() { 02299 $inArray = func_get_args(); 02300 $retVal = array(); 02301 02302 foreach ( $inArray as $name ) { 02303 $retVal[$name] = $this->tableName( $name ); 02304 } 02305 02306 return $retVal; 02307 } 02308 02320 public function tableNamesN() { 02321 $inArray = func_get_args(); 02322 $retVal = array(); 02323 02324 foreach ( $inArray as $name ) { 02325 $retVal[] = $this->tableName( $name ); 02326 } 02327 02328 return $retVal; 02329 } 02330 02339 public function tableNameWithAlias( $name, $alias = false ) { 02340 if ( !$alias || $alias == $name ) { 02341 return $this->tableName( $name ); 02342 } else { 02343 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 02344 } 02345 } 02346 02353 public function tableNamesWithAlias( $tables ) { 02354 $retval = array(); 02355 foreach ( $tables as $alias => $table ) { 02356 if ( is_numeric( $alias ) ) { 02357 $alias = $table; 02358 } 02359 $retval[] = $this->tableNameWithAlias( $table, $alias ); 02360 } 02361 02362 return $retval; 02363 } 02364 02373 public function fieldNameWithAlias( $name, $alias = false ) { 02374 if ( !$alias || (string)$alias === (string)$name ) { 02375 return $name; 02376 } else { 02377 return $name . ' AS ' . $alias; //PostgreSQL needs AS 02378 } 02379 } 02380 02387 public function fieldNamesWithAlias( $fields ) { 02388 $retval = array(); 02389 foreach ( $fields as $alias => $field ) { 02390 if ( is_numeric( $alias ) ) { 02391 $alias = $field; 02392 } 02393 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 02394 } 02395 02396 return $retval; 02397 } 02398 02408 protected function tableNamesWithUseIndexOrJOIN( 02409 $tables, $use_index = array(), $join_conds = array() 02410 ) { 02411 $ret = array(); 02412 $retJOIN = array(); 02413 $use_index = (array)$use_index; 02414 $join_conds = (array)$join_conds; 02415 02416 foreach ( $tables as $alias => $table ) { 02417 if ( !is_string( $alias ) ) { 02418 // No alias? Set it equal to the table name 02419 $alias = $table; 02420 } 02421 // Is there a JOIN clause for this table? 02422 if ( isset( $join_conds[$alias] ) ) { 02423 list( $joinType, $conds ) = $join_conds[$alias]; 02424 $tableClause = $joinType; 02425 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 02426 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 02427 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 02428 if ( $use != '' ) { 02429 $tableClause .= ' ' . $use; 02430 } 02431 } 02432 $on = $this->makeList( (array)$conds, LIST_AND ); 02433 if ( $on != '' ) { 02434 $tableClause .= ' ON (' . $on . ')'; 02435 } 02436 02437 $retJOIN[] = $tableClause; 02438 } elseif ( isset( $use_index[$alias] ) ) { 02439 // Is there an INDEX clause for this table? 02440 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02441 $tableClause .= ' ' . $this->useIndexClause( 02442 implode( ',', (array)$use_index[$alias] ) 02443 ); 02444 02445 $ret[] = $tableClause; 02446 } else { 02447 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02448 02449 $ret[] = $tableClause; 02450 } 02451 } 02452 02453 // We can't separate explicit JOIN clauses with ',', use ' ' for those 02454 $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 02455 $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 02456 02457 // Compile our final table clause 02458 return implode( ' ', array( $implicitJoins, $explicitJoins ) ); 02459 } 02460 02467 protected function indexName( $index ) { 02468 // Backwards-compatibility hack 02469 $renamed = array( 02470 'ar_usertext_timestamp' => 'usertext_timestamp', 02471 'un_user_id' => 'user_id', 02472 'un_user_ip' => 'user_ip', 02473 ); 02474 02475 if ( isset( $renamed[$index] ) ) { 02476 return $renamed[$index]; 02477 } else { 02478 return $index; 02479 } 02480 } 02481 02488 public function addQuotes( $s ) { 02489 if ( $s === null ) { 02490 return 'NULL'; 02491 } else { 02492 # This will also quote numeric values. This should be harmless, 02493 # and protects against weird problems that occur when they really 02494 # _are_ strings such as article titles and string->number->string 02495 # conversion is not 1:1. 02496 return "'" . $this->strencode( $s ) . "'"; 02497 } 02498 } 02499 02509 public function addIdentifierQuotes( $s ) { 02510 return '"' . str_replace( '"', '""', $s ) . '"'; 02511 } 02512 02520 public function isQuotedIdentifier( $name ) { 02521 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 02522 } 02523 02528 protected function escapeLikeInternal( $s ) { 02529 $s = str_replace( '\\', '\\\\', $s ); 02530 $s = $this->strencode( $s ); 02531 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s ); 02532 02533 return $s; 02534 } 02535 02552 public function buildLike() { 02553 $params = func_get_args(); 02554 02555 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 02556 $params = $params[0]; 02557 } 02558 02559 $s = ''; 02560 02561 foreach ( $params as $value ) { 02562 if ( $value instanceof LikeMatch ) { 02563 $s .= $value->toString(); 02564 } else { 02565 $s .= $this->escapeLikeInternal( $value ); 02566 } 02567 } 02568 02569 return " LIKE '" . $s . "' "; 02570 } 02571 02577 public function anyChar() { 02578 return new LikeMatch( '_' ); 02579 } 02580 02586 public function anyString() { 02587 return new LikeMatch( '%' ); 02588 } 02589 02601 public function nextSequenceValue( $seqName ) { 02602 return null; 02603 } 02604 02615 public function useIndexClause( $index ) { 02616 return ''; 02617 } 02618 02641 public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { 02642 $quotedTable = $this->tableName( $table ); 02643 02644 if ( count( $rows ) == 0 ) { 02645 return; 02646 } 02647 02648 # Single row case 02649 if ( !is_array( reset( $rows ) ) ) { 02650 $rows = array( $rows ); 02651 } 02652 02653 foreach ( $rows as $row ) { 02654 # Delete rows which collide 02655 if ( $uniqueIndexes ) { 02656 $sql = "DELETE FROM $quotedTable WHERE "; 02657 $first = true; 02658 foreach ( $uniqueIndexes as $index ) { 02659 if ( $first ) { 02660 $first = false; 02661 $sql .= '( '; 02662 } else { 02663 $sql .= ' ) OR ( '; 02664 } 02665 if ( is_array( $index ) ) { 02666 $first2 = true; 02667 foreach ( $index as $col ) { 02668 if ( $first2 ) { 02669 $first2 = false; 02670 } else { 02671 $sql .= ' AND '; 02672 } 02673 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 02674 } 02675 } else { 02676 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 02677 } 02678 } 02679 $sql .= ' )'; 02680 $this->query( $sql, $fname ); 02681 } 02682 02683 # Now insert the row 02684 $this->insert( $table, $row, $fname ); 02685 } 02686 } 02687 02698 protected function nativeReplace( $table, $rows, $fname ) { 02699 $table = $this->tableName( $table ); 02700 02701 # Single row case 02702 if ( !is_array( reset( $rows ) ) ) { 02703 $rows = array( $rows ); 02704 } 02705 02706 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 02707 $first = true; 02708 02709 foreach ( $rows as $row ) { 02710 if ( $first ) { 02711 $first = false; 02712 } else { 02713 $sql .= ','; 02714 } 02715 02716 $sql .= '(' . $this->makeList( $row ) . ')'; 02717 } 02718 02719 return $this->query( $sql, $fname ); 02720 } 02721 02756 public function upsert( $table, array $rows, array $uniqueIndexes, array $set, 02757 $fname = __METHOD__ 02758 ) { 02759 if ( !count( $rows ) ) { 02760 return true; // nothing to do 02761 } 02762 02763 if ( !is_array( reset( $rows ) ) ) { 02764 $rows = array( $rows ); 02765 } 02766 02767 if ( count( $uniqueIndexes ) ) { 02768 $clauses = array(); // list WHERE clauses that each identify a single row 02769 foreach ( $rows as $row ) { 02770 foreach ( $uniqueIndexes as $index ) { 02771 $index = is_array( $index ) ? $index : array( $index ); // columns 02772 $rowKey = array(); // unique key to this row 02773 foreach ( $index as $column ) { 02774 $rowKey[$column] = $row[$column]; 02775 } 02776 $clauses[] = $this->makeList( $rowKey, LIST_AND ); 02777 } 02778 } 02779 $where = array( $this->makeList( $clauses, LIST_OR ) ); 02780 } else { 02781 $where = false; 02782 } 02783 02784 $useTrx = !$this->mTrxLevel; 02785 if ( $useTrx ) { 02786 $this->begin( $fname ); 02787 } 02788 try { 02789 # Update any existing conflicting row(s) 02790 if ( $where !== false ) { 02791 $ok = $this->update( $table, $set, $where, $fname ); 02792 } else { 02793 $ok = true; 02794 } 02795 # Now insert any non-conflicting row(s) 02796 $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok; 02797 } catch ( Exception $e ) { 02798 if ( $useTrx ) { 02799 $this->rollback( $fname ); 02800 } 02801 throw $e; 02802 } 02803 if ( $useTrx ) { 02804 $this->commit( $fname ); 02805 } 02806 02807 return $ok; 02808 } 02809 02830 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 02831 $fname = __METHOD__ 02832 ) { 02833 if ( !$conds ) { 02834 throw new DBUnexpectedError( $this, 02835 'DatabaseBase::deleteJoin() called with empty $conds' ); 02836 } 02837 02838 $delTable = $this->tableName( $delTable ); 02839 $joinTable = $this->tableName( $joinTable ); 02840 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 02841 if ( $conds != '*' ) { 02842 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 02843 } 02844 $sql .= ')'; 02845 02846 $this->query( $sql, $fname ); 02847 } 02848 02856 public function textFieldSize( $table, $field ) { 02857 $table = $this->tableName( $table ); 02858 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 02859 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 02860 $row = $this->fetchObject( $res ); 02861 02862 $m = array(); 02863 02864 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 02865 $size = $m[1]; 02866 } else { 02867 $size = -1; 02868 } 02869 02870 return $size; 02871 } 02872 02881 public function lowPriorityOption() { 02882 return ''; 02883 } 02884 02895 public function delete( $table, $conds, $fname = __METHOD__ ) { 02896 if ( !$conds ) { 02897 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 02898 } 02899 02900 $table = $this->tableName( $table ); 02901 $sql = "DELETE FROM $table"; 02902 02903 if ( $conds != '*' ) { 02904 if ( is_array( $conds ) ) { 02905 $conds = $this->makeList( $conds, LIST_AND ); 02906 } 02907 $sql .= ' WHERE ' . $conds; 02908 } 02909 02910 return $this->query( $sql, $fname ); 02911 } 02912 02939 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 02940 $fname = __METHOD__, 02941 $insertOptions = array(), $selectOptions = array() 02942 ) { 02943 $destTable = $this->tableName( $destTable ); 02944 02945 if ( !is_array( $insertOptions ) ) { 02946 $insertOptions = array( $insertOptions ); 02947 } 02948 02949 $insertOptions = $this->makeInsertOptions( $insertOptions ); 02950 02951 if ( !is_array( $selectOptions ) ) { 02952 $selectOptions = array( $selectOptions ); 02953 } 02954 02955 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 02956 02957 if ( is_array( $srcTable ) ) { 02958 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 02959 } else { 02960 $srcTable = $this->tableName( $srcTable ); 02961 } 02962 02963 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 02964 " SELECT $startOpts " . implode( ',', $varMap ) . 02965 " FROM $srcTable $useIndex "; 02966 02967 if ( $conds != '*' ) { 02968 if ( is_array( $conds ) ) { 02969 $conds = $this->makeList( $conds, LIST_AND ); 02970 } 02971 $sql .= " WHERE $conds"; 02972 } 02973 02974 $sql .= " $tailOpts"; 02975 02976 return $this->query( $sql, $fname ); 02977 } 02978 02998 public function limitResult( $sql, $limit, $offset = false ) { 02999 if ( !is_numeric( $limit ) ) { 03000 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 03001 } 03002 03003 return "$sql LIMIT " 03004 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 03005 . "{$limit} "; 03006 } 03007 03013 public function unionSupportsOrderAndLimit() { 03014 return true; // True for almost every DB supported 03015 } 03016 03025 public function unionQueries( $sqls, $all ) { 03026 $glue = $all ? ') UNION ALL (' : ') UNION ('; 03027 03028 return '(' . implode( $glue, $sqls ) . ')'; 03029 } 03030 03040 public function conditional( $cond, $trueVal, $falseVal ) { 03041 if ( is_array( $cond ) ) { 03042 $cond = $this->makeList( $cond, LIST_AND ); 03043 } 03044 03045 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 03046 } 03047 03058 public function strreplace( $orig, $old, $new ) { 03059 return "REPLACE({$orig}, {$old}, {$new})"; 03060 } 03061 03068 public function getServerUptime() { 03069 return 0; 03070 } 03071 03078 public function wasDeadlock() { 03079 return false; 03080 } 03081 03088 public function wasLockTimeout() { 03089 return false; 03090 } 03091 03099 public function wasErrorReissuable() { 03100 return false; 03101 } 03102 03109 public function wasReadOnlyError() { 03110 return false; 03111 } 03112 03131 public function deadlockLoop() { 03132 $this->begin( __METHOD__ ); 03133 $args = func_get_args(); 03134 $function = array_shift( $args ); 03135 $oldIgnore = $this->ignoreErrors( true ); 03136 $tries = self::DEADLOCK_TRIES; 03137 03138 if ( is_array( $function ) ) { 03139 $fname = $function[0]; 03140 } else { 03141 $fname = $function; 03142 } 03143 03144 do { 03145 $retVal = call_user_func_array( $function, $args ); 03146 $error = $this->lastError(); 03147 $errno = $this->lastErrno(); 03148 $sql = $this->lastQuery(); 03149 03150 if ( $errno ) { 03151 if ( $this->wasDeadlock() ) { 03152 # Retry 03153 usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) ); 03154 } else { 03155 $this->reportQueryError( $error, $errno, $sql, $fname ); 03156 } 03157 } 03158 } while ( $this->wasDeadlock() && --$tries > 0 ); 03159 03160 $this->ignoreErrors( $oldIgnore ); 03161 03162 if ( $tries <= 0 ) { 03163 $this->rollback( __METHOD__ ); 03164 $this->reportQueryError( $error, $errno, $sql, $fname ); 03165 03166 return false; 03167 } else { 03168 $this->commit( __METHOD__ ); 03169 03170 return $retVal; 03171 } 03172 } 03173 03184 public function masterPosWait( DBMasterPos $pos, $timeout ) { 03185 # Real waits are implemented in the subclass. 03186 return 0; 03187 } 03188 03194 public function getSlavePos() { 03195 # Stub 03196 return false; 03197 } 03198 03204 public function getMasterPos() { 03205 # Stub 03206 return false; 03207 } 03208 03223 final public function onTransactionIdle( $callback ) { 03224 $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() ); 03225 if ( !$this->mTrxLevel ) { 03226 $this->runOnTransactionIdleCallbacks(); 03227 } 03228 } 03229 03241 final public function onTransactionPreCommitOrIdle( $callback ) { 03242 if ( $this->mTrxLevel ) { 03243 $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() ); 03244 } else { 03245 $this->onTransactionIdle( $callback ); // this will trigger immediately 03246 } 03247 } 03248 03254 protected function runOnTransactionIdleCallbacks() { 03255 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? 03256 03257 $e = null; // last exception 03258 do { // callbacks may add callbacks :) 03259 $callbacks = $this->mTrxIdleCallbacks; 03260 $this->mTrxIdleCallbacks = array(); // recursion guard 03261 foreach ( $callbacks as $callback ) { 03262 try { 03263 list( $phpCallback ) = $callback; 03264 $this->clearFlag( DBO_TRX ); // make each query its own transaction 03265 call_user_func( $phpCallback ); 03266 $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() 03267 } catch ( Exception $e ) { 03268 } 03269 } 03270 } while ( count( $this->mTrxIdleCallbacks ) ); 03271 03272 if ( $e instanceof Exception ) { 03273 throw $e; // re-throw any last exception 03274 } 03275 } 03276 03282 protected function runOnTransactionPreCommitCallbacks() { 03283 $e = null; // last exception 03284 do { // callbacks may add callbacks :) 03285 $callbacks = $this->mTrxPreCommitCallbacks; 03286 $this->mTrxPreCommitCallbacks = array(); // recursion guard 03287 foreach ( $callbacks as $callback ) { 03288 try { 03289 list( $phpCallback ) = $callback; 03290 call_user_func( $phpCallback ); 03291 } catch ( Exception $e ) { 03292 } 03293 } 03294 } while ( count( $this->mTrxPreCommitCallbacks ) ); 03295 03296 if ( $e instanceof Exception ) { 03297 throw $e; // re-throw any last exception 03298 } 03299 } 03300 03325 final public function startAtomic( $fname = __METHOD__ ) { 03326 if ( !$this->mTrxLevel ) { 03327 $this->begin( $fname ); 03328 $this->mTrxAutomatic = true; 03329 $this->mTrxAutomaticAtomic = true; 03330 } 03331 03332 $this->mTrxAtomicLevels->push( $fname ); 03333 } 03334 03346 final public function endAtomic( $fname = __METHOD__ ) { 03347 if ( !$this->mTrxLevel ) { 03348 throw new DBUnexpectedError( $this, 'No atomic transaction is open.' ); 03349 } 03350 if ( $this->mTrxAtomicLevels->isEmpty() || 03351 $this->mTrxAtomicLevels->pop() !== $fname 03352 ) { 03353 throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' ); 03354 } 03355 03356 if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) { 03357 $this->commit( $fname, 'flush' ); 03358 } 03359 } 03360 03376 final public function begin( $fname = __METHOD__ ) { 03377 global $wgDebugDBTransactions; 03378 03379 if ( $this->mTrxLevel ) { // implicit commit 03380 if ( !$this->mTrxAtomicLevels->isEmpty() ) { 03381 // If the current transaction was an automatic atomic one, then we definitely have 03382 // a problem. Same if there is any unclosed atomic level. 03383 throw new DBUnexpectedError( $this, 03384 "Attempted to start explicit transaction when atomic levels are still open." 03385 ); 03386 } elseif ( !$this->mTrxAutomatic ) { 03387 // We want to warn about inadvertently nested begin/commit pairs, but not about 03388 // auto-committing implicit transactions that were started by query() via DBO_TRX 03389 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " . 03390 " performing implicit commit!"; 03391 wfWarn( $msg ); 03392 wfLogDBError( $msg ); 03393 } else { 03394 // if the transaction was automatic and has done write operations, 03395 // log it if $wgDebugDBTransactions is enabled. 03396 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { 03397 wfDebug( "$fname: Automatic transaction with writes in progress" . 03398 " (from {$this->mTrxFname}), performing implicit commit!\n" 03399 ); 03400 } 03401 } 03402 03403 $this->runOnTransactionPreCommitCallbacks(); 03404 $this->doCommit( $fname ); 03405 if ( $this->mTrxDoneWrites ) { 03406 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); 03407 } 03408 $this->runOnTransactionIdleCallbacks(); 03409 } 03410 03411 $this->doBegin( $fname ); 03412 $this->mTrxFname = $fname; 03413 $this->mTrxDoneWrites = false; 03414 $this->mTrxAutomatic = false; 03415 $this->mTrxAutomaticAtomic = false; 03416 $this->mTrxAtomicLevels = new SplStack; 03417 $this->mTrxIdleCallbacks = array(); 03418 $this->mTrxPreCommitCallbacks = array(); 03419 } 03420 03427 protected function doBegin( $fname ) { 03428 $this->query( 'BEGIN', $fname ); 03429 $this->mTrxLevel = 1; 03430 } 03431 03446 final public function commit( $fname = __METHOD__, $flush = '' ) { 03447 if ( !$this->mTrxAtomicLevels->isEmpty() ) { 03448 // There are still atomic sections open. This cannot be ignored 03449 throw new DBUnexpectedError( 03450 $this, 03451 "Attempted to commit transaction while atomic sections are still open" 03452 ); 03453 } 03454 03455 if ( $flush !== 'flush' ) { 03456 if ( !$this->mTrxLevel ) { 03457 wfWarn( "$fname: No transaction to commit, something got out of sync!" ); 03458 } elseif ( $this->mTrxAutomatic ) { 03459 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); 03460 } 03461 } else { 03462 if ( !$this->mTrxLevel ) { 03463 return; // nothing to do 03464 } elseif ( !$this->mTrxAutomatic ) { 03465 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03466 } 03467 } 03468 03469 $this->runOnTransactionPreCommitCallbacks(); 03470 $this->doCommit( $fname ); 03471 if ( $this->mTrxDoneWrites ) { 03472 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); 03473 } 03474 $this->runOnTransactionIdleCallbacks(); 03475 } 03476 03483 protected function doCommit( $fname ) { 03484 if ( $this->mTrxLevel ) { 03485 $this->query( 'COMMIT', $fname ); 03486 $this->mTrxLevel = 0; 03487 } 03488 } 03489 03503 final public function rollback( $fname = __METHOD__, $flush = '' ) { 03504 if ( $flush !== 'flush' ) { 03505 if ( !$this->mTrxLevel ) { 03506 wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); 03507 } elseif ( $this->mTrxAutomatic ) { 03508 wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" ); 03509 } 03510 } else { 03511 if ( !$this->mTrxLevel ) { 03512 return; // nothing to do 03513 } elseif ( !$this->mTrxAutomatic ) { 03514 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 03515 } 03516 } 03517 03518 $this->doRollback( $fname ); 03519 $this->mTrxIdleCallbacks = array(); // cancel 03520 $this->mTrxPreCommitCallbacks = array(); // cancel 03521 $this->mTrxAtomicLevels = new SplStack; 03522 if ( $this->mTrxDoneWrites ) { 03523 Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname ); 03524 } 03525 } 03526 03533 protected function doRollback( $fname ) { 03534 if ( $this->mTrxLevel ) { 03535 $this->query( 'ROLLBACK', $fname, true ); 03536 $this->mTrxLevel = 0; 03537 } 03538 } 03539 03555 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 03556 $fname = __METHOD__ 03557 ) { 03558 throw new MWException( 03559 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 03560 } 03561 03569 function listTables( $prefix = null, $fname = __METHOD__ ) { 03570 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 03571 } 03572 03577 final public function clearViewsCache() { 03578 $this->allViews = null; 03579 } 03580 03592 public function listViews( $prefix = null, $fname = __METHOD__ ) { 03593 throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' ); 03594 } 03595 03603 public function isView( $name ) { 03604 throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' ); 03605 } 03606 03618 public function timestamp( $ts = 0 ) { 03619 return wfTimestamp( TS_MW, $ts ); 03620 } 03621 03635 public function timestampOrNull( $ts = null ) { 03636 if ( is_null( $ts ) ) { 03637 return null; 03638 } else { 03639 return $this->timestamp( $ts ); 03640 } 03641 } 03642 03657 public function resultObject( $result ) { 03658 if ( empty( $result ) ) { 03659 return false; 03660 } elseif ( $result instanceof ResultWrapper ) { 03661 return $result; 03662 } elseif ( $result === true ) { 03663 // Successful write query 03664 return $result; 03665 } else { 03666 return new ResultWrapper( $this, $result ); 03667 } 03668 } 03669 03675 public function ping() { 03676 # Stub. Not essential to override. 03677 return true; 03678 } 03679 03689 public function getLag() { 03690 return 0; 03691 } 03692 03698 function maxListLen() { 03699 return 0; 03700 } 03701 03711 public function encodeBlob( $b ) { 03712 return $b; 03713 } 03714 03723 public function decodeBlob( $b ) { 03724 return $b; 03725 } 03726 03737 public function setSessionOptions( array $options ) { 03738 } 03739 03756 public function sourceFile( 03757 $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false 03758 ) { 03759 wfSuppressWarnings(); 03760 $fp = fopen( $filename, 'r' ); 03761 wfRestoreWarnings(); 03762 03763 if ( false === $fp ) { 03764 throw new MWException( "Could not open \"{$filename}\".\n" ); 03765 } 03766 03767 if ( !$fname ) { 03768 $fname = __METHOD__ . "( $filename )"; 03769 } 03770 03771 try { 03772 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); 03773 } catch ( MWException $e ) { 03774 fclose( $fp ); 03775 throw $e; 03776 } 03777 03778 fclose( $fp ); 03779 03780 return $error; 03781 } 03782 03791 public function patchPath( $patch ) { 03792 global $IP; 03793 03794 $dbType = $this->getType(); 03795 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 03796 return "$IP/maintenance/$dbType/archives/$patch"; 03797 } else { 03798 return "$IP/maintenance/archives/$patch"; 03799 } 03800 } 03801 03809 public function setSchemaVars( $vars ) { 03810 $this->mSchemaVars = $vars; 03811 } 03812 03826 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 03827 $fname = __METHOD__, $inputCallback = false 03828 ) { 03829 $cmd = ''; 03830 03831 while ( !feof( $fp ) ) { 03832 if ( $lineCallback ) { 03833 call_user_func( $lineCallback ); 03834 } 03835 03836 $line = trim( fgets( $fp ) ); 03837 03838 if ( $line == '' ) { 03839 continue; 03840 } 03841 03842 if ( '-' == $line[0] && '-' == $line[1] ) { 03843 continue; 03844 } 03845 03846 if ( $cmd != '' ) { 03847 $cmd .= ' '; 03848 } 03849 03850 $done = $this->streamStatementEnd( $cmd, $line ); 03851 03852 $cmd .= "$line\n"; 03853 03854 if ( $done || feof( $fp ) ) { 03855 $cmd = $this->replaceVars( $cmd ); 03856 03857 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { 03858 $res = $this->query( $cmd, $fname ); 03859 03860 if ( $resultCallback ) { 03861 call_user_func( $resultCallback, $res, $this ); 03862 } 03863 03864 if ( false === $res ) { 03865 $err = $this->lastError(); 03866 03867 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 03868 } 03869 } 03870 $cmd = ''; 03871 } 03872 } 03873 03874 return true; 03875 } 03876 03884 public function streamStatementEnd( &$sql, &$newLine ) { 03885 if ( $this->delimiter ) { 03886 $prev = $newLine; 03887 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 03888 if ( $newLine != $prev ) { 03889 return true; 03890 } 03891 } 03892 03893 return false; 03894 } 03895 03913 protected function replaceSchemaVars( $ins ) { 03914 $vars = $this->getSchemaVars(); 03915 foreach ( $vars as $var => $value ) { 03916 // replace '{$var}' 03917 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 03918 // replace `{$var}` 03919 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 03920 // replace /*$var*/ 03921 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); 03922 } 03923 03924 return $ins; 03925 } 03926 03933 protected function replaceVars( $ins ) { 03934 $ins = $this->replaceSchemaVars( $ins ); 03935 03936 // Table prefixes 03937 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 03938 array( $this, 'tableNameCallback' ), $ins ); 03939 03940 // Index names 03941 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 03942 array( $this, 'indexNameCallback' ), $ins ); 03943 03944 return $ins; 03945 } 03946 03953 protected function getSchemaVars() { 03954 if ( $this->mSchemaVars ) { 03955 return $this->mSchemaVars; 03956 } else { 03957 return $this->getDefaultSchemaVars(); 03958 } 03959 } 03960 03969 protected function getDefaultSchemaVars() { 03970 return array(); 03971 } 03972 03979 protected function tableNameCallback( $matches ) { 03980 return $this->tableName( $matches[1] ); 03981 } 03982 03989 protected function indexNameCallback( $matches ) { 03990 return $this->indexName( $matches[1] ); 03991 } 03992 04001 public function lockIsFree( $lockName, $method ) { 04002 return true; 04003 } 04004 04016 public function lock( $lockName, $method, $timeout = 5 ) { 04017 return true; 04018 } 04019 04030 public function unlock( $lockName, $method ) { 04031 return true; 04032 } 04033 04043 public function lockTables( $read, $write, $method, $lowPriority = true ) { 04044 return true; 04045 } 04046 04053 public function unlockTables( $method ) { 04054 return true; 04055 } 04056 04064 public function dropTable( $tableName, $fName = __METHOD__ ) { 04065 if ( !$this->tableExists( $tableName, $fName ) ) { 04066 return false; 04067 } 04068 $sql = "DROP TABLE " . $this->tableName( $tableName ); 04069 if ( $this->cascadingDeletes() ) { 04070 $sql .= " CASCADE"; 04071 } 04072 04073 return $this->query( $sql, $fName ); 04074 } 04075 04082 public function getSearchEngine() { 04083 return 'SearchEngineDummy'; 04084 } 04085 04093 public function getInfinity() { 04094 return 'infinity'; 04095 } 04096 04103 public function encodeExpiry( $expiry ) { 04104 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 04105 ? $this->getInfinity() 04106 : $this->timestamp( $expiry ); 04107 } 04108 04116 public function decodeExpiry( $expiry, $format = TS_MW ) { 04117 return ( $expiry == '' || $expiry == $this->getInfinity() ) 04118 ? 'infinity' 04119 : wfTimestamp( $format, $expiry ); 04120 } 04121 04131 public function setBigSelects( $value = true ) { 04132 // no-op 04133 } 04134 04138 public function __toString() { 04139 return (string)$this->mConn; 04140 } 04141 04145 public function __destruct() { 04146 if ( $this->mTrxLevel && $this->mTrxDoneWrites ) { 04147 trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." ); 04148 } 04149 if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) { 04150 $callers = array(); 04151 foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) { 04152 $callers[] = $callbackInfo[1]; 04153 } 04154 $callers = implode( ', ', $callers ); 04155 trigger_error( "DB transaction callbacks still pending (from $callers)." ); 04156 } 04157 } 04158 }