MediaWiki
REL1_20
|
00001 <?php 00028 define( 'DEADLOCK_TRIES', 4 ); 00030 define( 'DEADLOCK_DELAY_MIN', 500000 ); 00032 define( 'DEADLOCK_DELAY_MAX', 1500000 ); 00033 00041 interface DatabaseType { 00047 function getType(); 00048 00059 function open( $server, $user, $password, $dbName ); 00060 00070 function fetchObject( $res ); 00071 00080 function fetchRow( $res ); 00081 00088 function numRows( $res ); 00089 00097 function numFields( $res ); 00098 00107 function fieldName( $res, $n ); 00108 00121 function insertId(); 00122 00130 function dataSeek( $res, $row ); 00131 00138 function lastErrno(); 00139 00146 function lastError(); 00147 00157 function fieldInfo( $table, $field ); 00158 00166 function indexInfo( $table, $index, $fname = 'Database::indexInfo' ); 00167 00174 function affectedRows(); 00175 00182 function strencode( $s ); 00183 00192 static function getSoftwareLink(); 00193 00200 function getServerVersion(); 00201 00209 function getServerInfo(); 00210 } 00211 00216 abstract class DatabaseBase implements DatabaseType { 00217 00218 # ------------------------------------------------------------------------------ 00219 # Variables 00220 # ------------------------------------------------------------------------------ 00221 00222 protected $mLastQuery = ''; 00223 protected $mDoneWrites = false; 00224 protected $mPHPError = false; 00225 00226 protected $mServer, $mUser, $mPassword, $mDBname; 00227 00228 protected $mConn = null; 00229 protected $mOpened = false; 00230 00235 protected $mTrxIdleCallbacks = array(); 00236 00237 protected $mTablePrefix; 00238 protected $mFlags; 00239 protected $mTrxLevel = 0; 00240 protected $mErrorCount = 0; 00241 protected $mLBInfo = array(); 00242 protected $mFakeSlaveLag = null, $mFakeMaster = false; 00243 protected $mDefaultBigSelects = null; 00244 protected $mSchemaVars = false; 00245 00246 protected $preparedArgs; 00247 00248 protected $htmlErrors; 00249 00250 protected $delimiter = ';'; 00251 00252 # ------------------------------------------------------------------------------ 00253 # Accessors 00254 # ------------------------------------------------------------------------------ 00255 # These optionally set a variable and return the previous state 00256 00264 public function getServerInfo() { 00265 return $this->getServerVersion(); 00266 } 00267 00277 public function debug( $debug = null ) { 00278 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 00279 } 00280 00303 public function bufferResults( $buffer = null ) { 00304 if ( is_null( $buffer ) ) { 00305 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 00306 } else { 00307 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 00308 } 00309 } 00310 00322 public function ignoreErrors( $ignoreErrors = null ) { 00323 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 00324 } 00325 00335 public function trxLevel( $level = null ) { 00336 return wfSetVar( $this->mTrxLevel, $level ); 00337 } 00338 00344 public function errorCount( $count = null ) { 00345 return wfSetVar( $this->mErrorCount, $count ); 00346 } 00347 00353 public function tablePrefix( $prefix = null ) { 00354 return wfSetVar( $this->mTablePrefix, $prefix ); 00355 } 00356 00366 public function getLBInfo( $name = null ) { 00367 if ( is_null( $name ) ) { 00368 return $this->mLBInfo; 00369 } else { 00370 if ( array_key_exists( $name, $this->mLBInfo ) ) { 00371 return $this->mLBInfo[$name]; 00372 } else { 00373 return null; 00374 } 00375 } 00376 } 00377 00386 public function setLBInfo( $name, $value = null ) { 00387 if ( is_null( $value ) ) { 00388 $this->mLBInfo = $name; 00389 } else { 00390 $this->mLBInfo[$name] = $value; 00391 } 00392 } 00393 00399 public function setFakeSlaveLag( $lag ) { 00400 $this->mFakeSlaveLag = $lag; 00401 } 00402 00408 public function setFakeMaster( $enabled = true ) { 00409 $this->mFakeMaster = $enabled; 00410 } 00411 00417 public function cascadingDeletes() { 00418 return false; 00419 } 00420 00426 public function cleanupTriggers() { 00427 return false; 00428 } 00429 00436 public function strictIPs() { 00437 return false; 00438 } 00439 00445 public function realTimestamps() { 00446 return false; 00447 } 00448 00454 public function implicitGroupby() { 00455 return true; 00456 } 00457 00464 public function implicitOrderby() { 00465 return true; 00466 } 00467 00474 public function searchableIPs() { 00475 return false; 00476 } 00477 00483 public function functionalIndexes() { 00484 return false; 00485 } 00486 00491 public function lastQuery() { 00492 return $this->mLastQuery; 00493 } 00494 00501 public function doneWrites() { 00502 return $this->mDoneWrites; 00503 } 00504 00511 public function writesOrCallbacksPending() { 00512 return $this->mTrxLevel && ( $this->mDoneWrites || $this->mTrxIdleCallbacks ); 00513 } 00514 00519 public function isOpen() { 00520 return $this->mOpened; 00521 } 00522 00535 public function setFlag( $flag ) { 00536 global $wgDebugDBTransactions; 00537 $this->mFlags |= $flag; 00538 if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) { 00539 wfDebug("Implicit transactions are now disabled.\n"); 00540 } 00541 } 00542 00548 public function clearFlag( $flag ) { 00549 global $wgDebugDBTransactions; 00550 $this->mFlags &= ~$flag; 00551 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 00552 wfDebug("Implicit transactions are now disabled.\n"); 00553 } 00554 } 00555 00562 public function getFlag( $flag ) { 00563 return !!( $this->mFlags & $flag ); 00564 } 00565 00573 public function getProperty( $name ) { 00574 return $this->$name; 00575 } 00576 00580 public function getWikiID() { 00581 if ( $this->mTablePrefix ) { 00582 return "{$this->mDBname}-{$this->mTablePrefix}"; 00583 } else { 00584 return $this->mDBname; 00585 } 00586 } 00587 00593 public function getSchemaPath() { 00594 global $IP; 00595 if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) { 00596 return "$IP/maintenance/" . $this->getType() . "/tables.sql"; 00597 } else { 00598 return "$IP/maintenance/tables.sql"; 00599 } 00600 } 00601 00602 # ------------------------------------------------------------------------------ 00603 # Other functions 00604 # ------------------------------------------------------------------------------ 00605 00615 function __construct( $server = false, $user = false, $password = false, $dbName = false, 00616 $flags = 0, $tablePrefix = 'get from global' 00617 ) { 00618 global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions; 00619 00620 $this->mFlags = $flags; 00621 00622 if ( $this->mFlags & DBO_DEFAULT ) { 00623 if ( $wgCommandLineMode ) { 00624 $this->mFlags &= ~DBO_TRX; 00625 if ( $wgDebugDBTransactions ) { 00626 wfDebug("Implicit transaction open disabled.\n"); 00627 } 00628 } else { 00629 $this->mFlags |= DBO_TRX; 00630 if ( $wgDebugDBTransactions ) { 00631 wfDebug("Implicit transaction open enabled.\n"); 00632 } 00633 } 00634 } 00635 00637 if ( $tablePrefix == 'get from global' ) { 00638 $this->mTablePrefix = $wgDBprefix; 00639 } else { 00640 $this->mTablePrefix = $tablePrefix; 00641 } 00642 00643 if ( $user ) { 00644 $this->open( $server, $user, $password, $dbName ); 00645 } 00646 } 00647 00653 public function __sleep() { 00654 throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' ); 00655 } 00656 00679 public final static function factory( $dbType, $p = array() ) { 00680 $canonicalDBTypes = array( 00681 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2' 00682 ); 00683 $dbType = strtolower( $dbType ); 00684 $class = 'Database' . ucfirst( $dbType ); 00685 00686 if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) { 00687 return new $class( 00688 isset( $p['host'] ) ? $p['host'] : false, 00689 isset( $p['user'] ) ? $p['user'] : false, 00690 isset( $p['password'] ) ? $p['password'] : false, 00691 isset( $p['dbname'] ) ? $p['dbname'] : false, 00692 isset( $p['flags'] ) ? $p['flags'] : 0, 00693 isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global' 00694 ); 00695 } else { 00696 return null; 00697 } 00698 } 00699 00700 protected function installErrorHandler() { 00701 $this->mPHPError = false; 00702 $this->htmlErrors = ini_set( 'html_errors', '0' ); 00703 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 00704 } 00705 00709 protected function restoreErrorHandler() { 00710 restore_error_handler(); 00711 if ( $this->htmlErrors !== false ) { 00712 ini_set( 'html_errors', $this->htmlErrors ); 00713 } 00714 if ( $this->mPHPError ) { 00715 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 00716 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 00717 return $error; 00718 } else { 00719 return false; 00720 } 00721 } 00722 00727 protected function connectionErrorHandler( $errno, $errstr ) { 00728 $this->mPHPError = $errstr; 00729 } 00730 00737 public function close() { 00738 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 00739 throw new MWException( "Transaction idle callbacks still pending." ); 00740 } 00741 $this->mOpened = false; 00742 if ( $this->mConn ) { 00743 if ( $this->trxLevel() ) { 00744 $this->commit( __METHOD__ ); 00745 } 00746 $ret = $this->closeConnection(); 00747 $this->mConn = false; 00748 return $ret; 00749 } else { 00750 return true; 00751 } 00752 } 00753 00759 protected abstract function closeConnection(); 00760 00764 function reportConnectionError( $error = 'Unknown error' ) { 00765 $myError = $this->lastError(); 00766 if ( $myError ) { 00767 $error = $myError; 00768 } 00769 00770 # New method 00771 throw new DBConnectionError( $this, $error ); 00772 } 00773 00780 protected abstract function doQuery( $sql ); 00781 00790 public function isWriteQuery( $sql ) { 00791 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 00792 } 00793 00816 public function query( $sql, $fname = '', $tempIgnore = false ) { 00817 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 00818 if ( !Profiler::instance()->isStub() ) { 00819 # generalizeSQL will probably cut down the query to reasonable 00820 # logging size most of the time. The substr is really just a sanity check. 00821 00822 if ( $isMaster ) { 00823 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00824 $totalProf = 'DatabaseBase::query-master'; 00825 } else { 00826 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 00827 $totalProf = 'DatabaseBase::query'; 00828 } 00829 00830 wfProfileIn( $totalProf ); 00831 wfProfileIn( $queryProf ); 00832 } 00833 00834 $this->mLastQuery = $sql; 00835 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { 00836 # Set a flag indicating that writes have been done 00837 wfDebug( __METHOD__ . ": Writes done: $sql\n" ); 00838 $this->mDoneWrites = true; 00839 } 00840 00841 # Add a comment for easy SHOW PROCESSLIST interpretation 00842 global $wgUser; 00843 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 00844 $userName = $wgUser->getName(); 00845 if ( mb_strlen( $userName ) > 15 ) { 00846 $userName = mb_substr( $userName, 0, 15 ) . '...'; 00847 } 00848 $userName = str_replace( '/', '', $userName ); 00849 } else { 00850 $userName = ''; 00851 } 00852 $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 ); 00853 00854 # If DBO_TRX is set, start a transaction 00855 if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() && 00856 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) { 00857 # avoid establishing transactions for SHOW and SET statements too - 00858 # that would delay transaction initializations to once connection 00859 # is really used by application 00860 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 00861 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 00862 global $wgDebugDBTransactions; 00863 if ( $wgDebugDBTransactions ) { 00864 wfDebug("Implicit transaction start.\n"); 00865 } 00866 $this->begin( __METHOD__ . " ($fname)" ); 00867 } 00868 } 00869 00870 if ( $this->debug() ) { 00871 static $cnt = 0; 00872 00873 $cnt++; 00874 $sqlx = substr( $commentedSql, 0, 500 ); 00875 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 00876 00877 $master = $isMaster ? 'master' : 'slave'; 00878 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 00879 } 00880 00881 if ( istainted( $sql ) & TC_MYSQL ) { 00882 throw new MWException( 'Tainted query found' ); 00883 } 00884 00885 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 00886 00887 # Do the query and handle errors 00888 $ret = $this->doQuery( $commentedSql ); 00889 00890 MWDebug::queryTime( $queryId ); 00891 00892 # Try reconnecting if the connection was lost 00893 if ( false === $ret && $this->wasErrorReissuable() ) { 00894 # Transaction is gone, like it or not 00895 $this->mTrxLevel = 0; 00896 $this->mTrxIdleCallbacks = array(); // cancel 00897 wfDebug( "Connection lost, reconnecting...\n" ); 00898 00899 if ( $this->ping() ) { 00900 wfDebug( "Reconnected\n" ); 00901 $sqlx = substr( $commentedSql, 0, 500 ); 00902 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 00903 global $wgRequestTime; 00904 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 00905 if ( $elapsed < 300 ) { 00906 # Not a database error to lose a transaction after a minute or two 00907 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); 00908 } 00909 $ret = $this->doQuery( $commentedSql ); 00910 } else { 00911 wfDebug( "Failed\n" ); 00912 } 00913 } 00914 00915 if ( false === $ret ) { 00916 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 00917 } 00918 00919 if ( !Profiler::instance()->isStub() ) { 00920 wfProfileOut( $queryProf ); 00921 wfProfileOut( $totalProf ); 00922 } 00923 00924 return $this->resultObject( $ret ); 00925 } 00926 00937 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 00938 # Ignore errors during error handling to avoid infinite recursion 00939 $ignore = $this->ignoreErrors( true ); 00940 ++$this->mErrorCount; 00941 00942 if ( $ignore || $tempIgnore ) { 00943 wfDebug( "SQL ERROR (ignored): $error\n" ); 00944 $this->ignoreErrors( $ignore ); 00945 } else { 00946 $sql1line = str_replace( "\n", "\\n", $sql ); 00947 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" ); 00948 wfDebug( "SQL ERROR: " . $error . "\n" ); 00949 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 00950 } 00951 } 00952 00967 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 00968 /* MySQL doesn't support prepared statements (yet), so just 00969 pack up the query for reference. We'll manually replace 00970 the bits later. */ 00971 return array( 'query' => $sql, 'func' => $func ); 00972 } 00973 00978 protected function freePrepared( $prepared ) { 00979 /* No-op by default */ 00980 } 00981 00989 public function execute( $prepared, $args = null ) { 00990 if ( !is_array( $args ) ) { 00991 # Pull the var args 00992 $args = func_get_args(); 00993 array_shift( $args ); 00994 } 00995 00996 $sql = $this->fillPrepared( $prepared['query'], $args ); 00997 00998 return $this->query( $sql, $prepared['func'] ); 00999 } 01000 01008 public function fillPrepared( $preparedQuery, $args ) { 01009 reset( $args ); 01010 $this->preparedArgs =& $args; 01011 01012 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 01013 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 01014 } 01015 01024 protected function fillPreparedArg( $matches ) { 01025 switch( $matches[1] ) { 01026 case '\\?': return '?'; 01027 case '\\!': return '!'; 01028 case '\\&': return '&'; 01029 } 01030 01031 list( /* $n */ , $arg ) = each( $this->preparedArgs ); 01032 01033 switch( $matches[1] ) { 01034 case '?': return $this->addQuotes( $arg ); 01035 case '!': return $arg; 01036 case '&': 01037 # return $this->addQuotes( file_get_contents( $arg ) ); 01038 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); 01039 default: 01040 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); 01041 } 01042 } 01043 01051 public function freeResult( $res ) {} 01052 01070 public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField', 01071 $options = array() ) 01072 { 01073 if ( !is_array( $options ) ) { 01074 $options = array( $options ); 01075 } 01076 01077 $options['LIMIT'] = 1; 01078 01079 $res = $this->select( $table, $var, $cond, $fname, $options ); 01080 01081 if ( $res === false || !$this->numRows( $res ) ) { 01082 return false; 01083 } 01084 01085 $row = $this->fetchRow( $res ); 01086 01087 if ( $row !== false ) { 01088 return reset( $row ); 01089 } else { 01090 return false; 01091 } 01092 } 01093 01103 public function makeSelectOptions( $options ) { 01104 $preLimitTail = $postLimitTail = ''; 01105 $startOpts = ''; 01106 01107 $noKeyOptions = array(); 01108 01109 foreach ( $options as $key => $option ) { 01110 if ( is_numeric( $key ) ) { 01111 $noKeyOptions[$option] = true; 01112 } 01113 } 01114 01115 if ( isset( $options['GROUP BY'] ) ) { 01116 $gb = is_array( $options['GROUP BY'] ) 01117 ? implode( ',', $options['GROUP BY'] ) 01118 : $options['GROUP BY']; 01119 $preLimitTail .= " GROUP BY {$gb}"; 01120 } 01121 01122 if ( isset( $options['HAVING'] ) ) { 01123 $having = is_array( $options['HAVING'] ) 01124 ? $this->makeList( $options['HAVING'], LIST_AND ) 01125 : $options['HAVING']; 01126 $preLimitTail .= " HAVING {$having}"; 01127 } 01128 01129 if ( isset( $options['ORDER BY'] ) ) { 01130 $ob = is_array( $options['ORDER BY'] ) 01131 ? implode( ',', $options['ORDER BY'] ) 01132 : $options['ORDER BY']; 01133 $preLimitTail .= " ORDER BY {$ob}"; 01134 } 01135 01136 // if (isset($options['LIMIT'])) { 01137 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 01138 // isset($options['OFFSET']) ? $options['OFFSET'] 01139 // : false); 01140 // } 01141 01142 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 01143 $postLimitTail .= ' FOR UPDATE'; 01144 } 01145 01146 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 01147 $postLimitTail .= ' LOCK IN SHARE MODE'; 01148 } 01149 01150 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01151 $startOpts .= 'DISTINCT'; 01152 } 01153 01154 # Various MySQL extensions 01155 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 01156 $startOpts .= ' /*! STRAIGHT_JOIN */'; 01157 } 01158 01159 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 01160 $startOpts .= ' HIGH_PRIORITY'; 01161 } 01162 01163 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 01164 $startOpts .= ' SQL_BIG_RESULT'; 01165 } 01166 01167 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 01168 $startOpts .= ' SQL_BUFFER_RESULT'; 01169 } 01170 01171 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 01172 $startOpts .= ' SQL_SMALL_RESULT'; 01173 } 01174 01175 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 01176 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 01177 } 01178 01179 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 01180 $startOpts .= ' SQL_CACHE'; 01181 } 01182 01183 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 01184 $startOpts .= ' SQL_NO_CACHE'; 01185 } 01186 01187 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { 01188 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01189 } else { 01190 $useIndex = ''; 01191 } 01192 01193 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01194 } 01195 01335 public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', 01336 $options = array(), $join_conds = array() ) { 01337 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 01338 01339 return $this->query( $sql, $fname ); 01340 } 01341 01358 public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', 01359 $options = array(), $join_conds = array() ) 01360 { 01361 if ( is_array( $vars ) ) { 01362 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 01363 } 01364 01365 $options = (array)$options; 01366 01367 if ( is_array( $table ) ) { 01368 $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 01369 ? $options['USE INDEX'] 01370 : array(); 01371 if ( count( $join_conds ) || count( $useIndex ) ) { 01372 $from = ' FROM ' . 01373 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds ); 01374 } else { 01375 $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) ); 01376 } 01377 } elseif ( $table != '' ) { 01378 if ( $table[0] == ' ' ) { 01379 $from = ' FROM ' . $table; 01380 } else { 01381 $from = ' FROM ' . $this->tableName( $table ); 01382 } 01383 } else { 01384 $from = ''; 01385 } 01386 01387 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options ); 01388 01389 if ( !empty( $conds ) ) { 01390 if ( is_array( $conds ) ) { 01391 $conds = $this->makeList( $conds, LIST_AND ); 01392 } 01393 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 01394 } else { 01395 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 01396 } 01397 01398 if ( isset( $options['LIMIT'] ) ) { 01399 $sql = $this->limitResult( $sql, $options['LIMIT'], 01400 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 01401 } 01402 $sql = "$sql $postLimitTail"; 01403 01404 if ( isset( $options['EXPLAIN'] ) ) { 01405 $sql = 'EXPLAIN ' . $sql; 01406 } 01407 01408 return $sql; 01409 } 01410 01425 public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow', 01426 $options = array(), $join_conds = array() ) 01427 { 01428 $options = (array)$options; 01429 $options['LIMIT'] = 1; 01430 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 01431 01432 if ( $res === false ) { 01433 return false; 01434 } 01435 01436 if ( !$this->numRows( $res ) ) { 01437 return false; 01438 } 01439 01440 $obj = $this->fetchObject( $res ); 01441 01442 return $obj; 01443 } 01444 01465 public function estimateRowCount( $table, $vars = '*', $conds = '', 01466 $fname = 'DatabaseBase::estimateRowCount', $options = array() ) 01467 { 01468 $rows = 0; 01469 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 01470 01471 if ( $res ) { 01472 $row = $this->fetchRow( $res ); 01473 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 01474 } 01475 01476 return $rows; 01477 } 01478 01487 static function generalizeSQL( $sql ) { 01488 # This does the same as the regexp below would do, but in such a way 01489 # as to avoid crashing php on some large strings. 01490 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); 01491 01492 $sql = str_replace ( "\\\\", '', $sql ); 01493 $sql = str_replace ( "\\'", '', $sql ); 01494 $sql = str_replace ( "\\\"", '', $sql ); 01495 $sql = preg_replace ( "/'.*'/s", "'X'", $sql ); 01496 $sql = preg_replace ( '/".*"/s', "'X'", $sql ); 01497 01498 # All newlines, tabs, etc replaced by single space 01499 $sql = preg_replace ( '/\s+/', ' ', $sql ); 01500 01501 # All numbers => N 01502 $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql ); 01503 01504 return $sql; 01505 } 01506 01515 public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) { 01516 $info = $this->fieldInfo( $table, $field ); 01517 01518 return (bool)$info; 01519 } 01520 01532 public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) { 01533 $info = $this->indexInfo( $table, $index, $fname ); 01534 if ( is_null( $info ) ) { 01535 return null; 01536 } else { 01537 return $info !== false; 01538 } 01539 } 01540 01549 public function tableExists( $table, $fname = __METHOD__ ) { 01550 $table = $this->tableName( $table ); 01551 $old = $this->ignoreErrors( true ); 01552 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 01553 $this->ignoreErrors( $old ); 01554 01555 return (bool)$res; 01556 } 01557 01564 public function fieldType( $res, $index ) { 01565 if ( $res instanceof ResultWrapper ) { 01566 $res = $res->result; 01567 } 01568 01569 return mysql_field_type( $res, $index ); 01570 } 01571 01580 public function indexUnique( $table, $index ) { 01581 $indexInfo = $this->indexInfo( $table, $index ); 01582 01583 if ( !$indexInfo ) { 01584 return null; 01585 } 01586 01587 return !$indexInfo[0]->Non_unique; 01588 } 01589 01596 protected function makeInsertOptions( $options ) { 01597 return implode( ' ', $options ); 01598 } 01599 01633 public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) { 01634 # No rows to insert, easy just return now 01635 if ( !count( $a ) ) { 01636 return true; 01637 } 01638 01639 $table = $this->tableName( $table ); 01640 01641 if ( !is_array( $options ) ) { 01642 $options = array( $options ); 01643 } 01644 01645 $options = $this->makeInsertOptions( $options ); 01646 01647 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 01648 $multi = true; 01649 $keys = array_keys( $a[0] ); 01650 } else { 01651 $multi = false; 01652 $keys = array_keys( $a ); 01653 } 01654 01655 $sql = 'INSERT ' . $options . 01656 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 01657 01658 if ( $multi ) { 01659 $first = true; 01660 foreach ( $a as $row ) { 01661 if ( $first ) { 01662 $first = false; 01663 } else { 01664 $sql .= ','; 01665 } 01666 $sql .= '(' . $this->makeList( $row ) . ')'; 01667 } 01668 } else { 01669 $sql .= '(' . $this->makeList( $a ) . ')'; 01670 } 01671 01672 return (bool)$this->query( $sql, $fname ); 01673 } 01674 01681 protected function makeUpdateOptions( $options ) { 01682 if ( !is_array( $options ) ) { 01683 $options = array( $options ); 01684 } 01685 01686 $opts = array(); 01687 01688 if ( in_array( 'LOW_PRIORITY', $options ) ) { 01689 $opts[] = $this->lowPriorityOption(); 01690 } 01691 01692 if ( in_array( 'IGNORE', $options ) ) { 01693 $opts[] = 'IGNORE'; 01694 } 01695 01696 return implode( ' ', $opts ); 01697 } 01698 01722 function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) { 01723 $table = $this->tableName( $table ); 01724 $opts = $this->makeUpdateOptions( $options ); 01725 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 01726 01727 if ( $conds !== array() && $conds !== '*' ) { 01728 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 01729 } 01730 01731 return $this->query( $sql, $fname ); 01732 } 01733 01747 public function makeList( $a, $mode = LIST_COMMA ) { 01748 if ( !is_array( $a ) ) { 01749 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 01750 } 01751 01752 $first = true; 01753 $list = ''; 01754 01755 foreach ( $a as $field => $value ) { 01756 if ( !$first ) { 01757 if ( $mode == LIST_AND ) { 01758 $list .= ' AND '; 01759 } elseif ( $mode == LIST_OR ) { 01760 $list .= ' OR '; 01761 } else { 01762 $list .= ','; 01763 } 01764 } else { 01765 $first = false; 01766 } 01767 01768 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 01769 $list .= "($value)"; 01770 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 01771 $list .= "$value"; 01772 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 01773 if ( count( $value ) == 0 ) { 01774 throw new MWException( __METHOD__ . ': empty input' ); 01775 } elseif ( count( $value ) == 1 ) { 01776 // Special-case single values, as IN isn't terribly efficient 01777 // Don't necessarily assume the single key is 0; we don't 01778 // enforce linear numeric ordering on other arrays here. 01779 $value = array_values( $value ); 01780 $list .= $field . " = " . $this->addQuotes( $value[0] ); 01781 } else { 01782 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 01783 } 01784 } elseif ( $value === null ) { 01785 if ( $mode == LIST_AND || $mode == LIST_OR ) { 01786 $list .= "$field IS "; 01787 } elseif ( $mode == LIST_SET ) { 01788 $list .= "$field = "; 01789 } 01790 $list .= 'NULL'; 01791 } else { 01792 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 01793 $list .= "$field = "; 01794 } 01795 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 01796 } 01797 } 01798 01799 return $list; 01800 } 01801 01812 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 01813 $conds = array(); 01814 01815 foreach ( $data as $base => $sub ) { 01816 if ( count( $sub ) ) { 01817 $conds[] = $this->makeList( 01818 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 01819 LIST_AND ); 01820 } 01821 } 01822 01823 if ( $conds ) { 01824 return $this->makeList( $conds, LIST_OR ); 01825 } else { 01826 // Nothing to search for... 01827 return false; 01828 } 01829 } 01830 01839 public function aggregateValue( $valuedata, $valuename = 'value' ) { 01840 return $valuename; 01841 } 01842 01847 public function bitNot( $field ) { 01848 return "(~$field)"; 01849 } 01850 01856 public function bitAnd( $fieldLeft, $fieldRight ) { 01857 return "($fieldLeft & $fieldRight)"; 01858 } 01859 01865 public function bitOr( $fieldLeft, $fieldRight ) { 01866 return "($fieldLeft | $fieldRight)"; 01867 } 01868 01874 public function buildConcat( $stringList ) { 01875 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 01876 } 01877 01887 public function selectDB( $db ) { 01888 # Stub. Shouldn't cause serious problems if it's not overridden, but 01889 # if your database engine supports a concept similar to MySQL's 01890 # databases you may as well. 01891 $this->mDBname = $db; 01892 return true; 01893 } 01894 01898 public function getDBname() { 01899 return $this->mDBname; 01900 } 01901 01905 public function getServer() { 01906 return $this->mServer; 01907 } 01908 01926 public function tableName( $name, $format = 'quoted' ) { 01927 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables; 01928 # Skip the entire process when we have a string quoted on both ends. 01929 # Note that we check the end so that we will still quote any use of 01930 # use of `database`.table. But won't break things if someone wants 01931 # to query a database table with a dot in the name. 01932 if ( $this->isQuotedIdentifier( $name ) ) { 01933 return $name; 01934 } 01935 01936 # Lets test for any bits of text that should never show up in a table 01937 # name. Basically anything like JOIN or ON which are actually part of 01938 # SQL queries, but may end up inside of the table value to combine 01939 # sql. Such as how the API is doing. 01940 # Note that we use a whitespace test rather than a \b test to avoid 01941 # any remote case where a word like on may be inside of a table name 01942 # surrounded by symbols which may be considered word breaks. 01943 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 01944 return $name; 01945 } 01946 01947 # Split database and table into proper variables. 01948 # We reverse the explode so that database.table and table both output 01949 # the correct table. 01950 $dbDetails = array_reverse( explode( '.', $name, 2 ) ); 01951 if ( isset( $dbDetails[1] ) ) { 01952 list( $table, $database ) = $dbDetails; 01953 } else { 01954 list( $table ) = $dbDetails; 01955 } 01956 $prefix = $this->mTablePrefix; # Default prefix 01957 01958 # A database name has been specified in input. We don't want any 01959 # prefixes added. 01960 if ( isset( $database ) ) { 01961 $prefix = ''; 01962 } 01963 01964 # Note that we use the long format because php will complain in in_array if 01965 # the input is not an array, and will complain in is_array if it is not set. 01966 if ( !isset( $database ) # Don't use shared database if pre selected. 01967 && isset( $wgSharedDB ) # We have a shared database 01968 && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`' 01969 && isset( $wgSharedTables ) 01970 && is_array( $wgSharedTables ) 01971 && in_array( $table, $wgSharedTables ) ) { # A shared table is selected 01972 $database = $wgSharedDB; 01973 $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix; 01974 } 01975 01976 # Quote the $database and $table and apply the prefix if not quoted. 01977 if ( isset( $database ) ) { 01978 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 01979 $database = $this->addIdentifierQuotes( $database ); 01980 } 01981 } 01982 01983 $table = "{$prefix}{$table}"; 01984 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) { 01985 $table = $this->addIdentifierQuotes( "{$table}" ); 01986 } 01987 01988 # Merge our database and table into our final table name. 01989 $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" ); 01990 01991 return $tableName; 01992 } 01993 02005 public function tableNames() { 02006 $inArray = func_get_args(); 02007 $retVal = array(); 02008 02009 foreach ( $inArray as $name ) { 02010 $retVal[$name] = $this->tableName( $name ); 02011 } 02012 02013 return $retVal; 02014 } 02015 02027 public function tableNamesN() { 02028 $inArray = func_get_args(); 02029 $retVal = array(); 02030 02031 foreach ( $inArray as $name ) { 02032 $retVal[] = $this->tableName( $name ); 02033 } 02034 02035 return $retVal; 02036 } 02037 02046 public function tableNameWithAlias( $name, $alias = false ) { 02047 if ( !$alias || $alias == $name ) { 02048 return $this->tableName( $name ); 02049 } else { 02050 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 02051 } 02052 } 02053 02060 public function tableNamesWithAlias( $tables ) { 02061 $retval = array(); 02062 foreach ( $tables as $alias => $table ) { 02063 if ( is_numeric( $alias ) ) { 02064 $alias = $table; 02065 } 02066 $retval[] = $this->tableNameWithAlias( $table, $alias ); 02067 } 02068 return $retval; 02069 } 02070 02079 public function fieldNameWithAlias( $name, $alias = false ) { 02080 if ( !$alias || (string)$alias === (string)$name ) { 02081 return $name; 02082 } else { 02083 return $name . ' AS ' . $alias; //PostgreSQL needs AS 02084 } 02085 } 02086 02093 public function fieldNamesWithAlias( $fields ) { 02094 $retval = array(); 02095 foreach ( $fields as $alias => $field ) { 02096 if ( is_numeric( $alias ) ) { 02097 $alias = $field; 02098 } 02099 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 02100 } 02101 return $retval; 02102 } 02103 02113 protected function tableNamesWithUseIndexOrJOIN( 02114 $tables, $use_index = array(), $join_conds = array() 02115 ) { 02116 $ret = array(); 02117 $retJOIN = array(); 02118 $use_index = (array)$use_index; 02119 $join_conds = (array)$join_conds; 02120 02121 foreach ( $tables as $alias => $table ) { 02122 if ( !is_string( $alias ) ) { 02123 // No alias? Set it equal to the table name 02124 $alias = $table; 02125 } 02126 // Is there a JOIN clause for this table? 02127 if ( isset( $join_conds[$alias] ) ) { 02128 list( $joinType, $conds ) = $join_conds[$alias]; 02129 $tableClause = $joinType; 02130 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 02131 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 02132 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 02133 if ( $use != '' ) { 02134 $tableClause .= ' ' . $use; 02135 } 02136 } 02137 $on = $this->makeList( (array)$conds, LIST_AND ); 02138 if ( $on != '' ) { 02139 $tableClause .= ' ON (' . $on . ')'; 02140 } 02141 02142 $retJOIN[] = $tableClause; 02143 // Is there an INDEX clause for this table? 02144 } elseif ( isset( $use_index[$alias] ) ) { 02145 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02146 $tableClause .= ' ' . $this->useIndexClause( 02147 implode( ',', (array)$use_index[$alias] ) ); 02148 02149 $ret[] = $tableClause; 02150 } else { 02151 $tableClause = $this->tableNameWithAlias( $table, $alias ); 02152 02153 $ret[] = $tableClause; 02154 } 02155 } 02156 02157 // We can't separate explicit JOIN clauses with ',', use ' ' for those 02158 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 02159 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 02160 02161 // Compile our final table clause 02162 return implode( ' ', array( $straightJoins, $otherJoins ) ); 02163 } 02164 02172 protected function indexName( $index ) { 02173 // Backwards-compatibility hack 02174 $renamed = array( 02175 'ar_usertext_timestamp' => 'usertext_timestamp', 02176 'un_user_id' => 'user_id', 02177 'un_user_ip' => 'user_ip', 02178 ); 02179 02180 if ( isset( $renamed[$index] ) ) { 02181 return $renamed[$index]; 02182 } else { 02183 return $index; 02184 } 02185 } 02186 02195 public function addQuotes( $s ) { 02196 if ( $s === null ) { 02197 return 'NULL'; 02198 } else { 02199 # This will also quote numeric values. This should be harmless, 02200 # and protects against weird problems that occur when they really 02201 # _are_ strings such as article titles and string->number->string 02202 # conversion is not 1:1. 02203 return "'" . $this->strencode( $s ) . "'"; 02204 } 02205 } 02206 02217 public function addIdentifierQuotes( $s ) { 02218 return '"' . str_replace( '"', '""', $s ) . '"'; 02219 } 02220 02229 public function isQuotedIdentifier( $name ) { 02230 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 02231 } 02232 02237 protected function escapeLikeInternal( $s ) { 02238 $s = str_replace( '\\', '\\\\', $s ); 02239 $s = $this->strencode( $s ); 02240 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s ); 02241 02242 return $s; 02243 } 02244 02257 public function buildLike() { 02258 $params = func_get_args(); 02259 02260 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 02261 $params = $params[0]; 02262 } 02263 02264 $s = ''; 02265 02266 foreach ( $params as $value ) { 02267 if ( $value instanceof LikeMatch ) { 02268 $s .= $value->toString(); 02269 } else { 02270 $s .= $this->escapeLikeInternal( $value ); 02271 } 02272 } 02273 02274 return " LIKE '" . $s . "' "; 02275 } 02276 02282 public function anyChar() { 02283 return new LikeMatch( '_' ); 02284 } 02285 02291 public function anyString() { 02292 return new LikeMatch( '%' ); 02293 } 02294 02306 public function nextSequenceValue( $seqName ) { 02307 return null; 02308 } 02309 02320 public function useIndexClause( $index ) { 02321 return ''; 02322 } 02323 02346 public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) { 02347 $quotedTable = $this->tableName( $table ); 02348 02349 if ( count( $rows ) == 0 ) { 02350 return; 02351 } 02352 02353 # Single row case 02354 if ( !is_array( reset( $rows ) ) ) { 02355 $rows = array( $rows ); 02356 } 02357 02358 foreach( $rows as $row ) { 02359 # Delete rows which collide 02360 if ( $uniqueIndexes ) { 02361 $sql = "DELETE FROM $quotedTable WHERE "; 02362 $first = true; 02363 foreach ( $uniqueIndexes as $index ) { 02364 if ( $first ) { 02365 $first = false; 02366 $sql .= '( '; 02367 } else { 02368 $sql .= ' ) OR ( '; 02369 } 02370 if ( is_array( $index ) ) { 02371 $first2 = true; 02372 foreach ( $index as $col ) { 02373 if ( $first2 ) { 02374 $first2 = false; 02375 } else { 02376 $sql .= ' AND '; 02377 } 02378 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 02379 } 02380 } else { 02381 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 02382 } 02383 } 02384 $sql .= ' )'; 02385 $this->query( $sql, $fname ); 02386 } 02387 02388 # Now insert the row 02389 $this->insert( $table, $row ); 02390 } 02391 } 02392 02403 protected function nativeReplace( $table, $rows, $fname ) { 02404 $table = $this->tableName( $table ); 02405 02406 # Single row case 02407 if ( !is_array( reset( $rows ) ) ) { 02408 $rows = array( $rows ); 02409 } 02410 02411 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 02412 $first = true; 02413 02414 foreach ( $rows as $row ) { 02415 if ( $first ) { 02416 $first = false; 02417 } else { 02418 $sql .= ','; 02419 } 02420 02421 $sql .= '(' . $this->makeList( $row ) . ')'; 02422 } 02423 02424 return $this->query( $sql, $fname ); 02425 } 02426 02447 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 02448 $fname = 'DatabaseBase::deleteJoin' ) 02449 { 02450 if ( !$conds ) { 02451 throw new DBUnexpectedError( $this, 02452 'DatabaseBase::deleteJoin() called with empty $conds' ); 02453 } 02454 02455 $delTable = $this->tableName( $delTable ); 02456 $joinTable = $this->tableName( $joinTable ); 02457 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 02458 if ( $conds != '*' ) { 02459 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 02460 } 02461 $sql .= ')'; 02462 02463 $this->query( $sql, $fname ); 02464 } 02465 02474 public function textFieldSize( $table, $field ) { 02475 $table = $this->tableName( $table ); 02476 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 02477 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 02478 $row = $this->fetchObject( $res ); 02479 02480 $m = array(); 02481 02482 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 02483 $size = $m[1]; 02484 } else { 02485 $size = -1; 02486 } 02487 02488 return $size; 02489 } 02490 02499 public function lowPriorityOption() { 02500 return ''; 02501 } 02502 02513 public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) { 02514 if ( !$conds ) { 02515 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 02516 } 02517 02518 $table = $this->tableName( $table ); 02519 $sql = "DELETE FROM $table"; 02520 02521 if ( $conds != '*' ) { 02522 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 02523 } 02524 02525 return $this->query( $sql, $fname ); 02526 } 02527 02554 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 02555 $fname = 'DatabaseBase::insertSelect', 02556 $insertOptions = array(), $selectOptions = array() ) 02557 { 02558 $destTable = $this->tableName( $destTable ); 02559 02560 if ( is_array( $insertOptions ) ) { 02561 $insertOptions = implode( ' ', $insertOptions ); 02562 } 02563 02564 if ( !is_array( $selectOptions ) ) { 02565 $selectOptions = array( $selectOptions ); 02566 } 02567 02568 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 02569 02570 if ( is_array( $srcTable ) ) { 02571 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 02572 } else { 02573 $srcTable = $this->tableName( $srcTable ); 02574 } 02575 02576 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 02577 " SELECT $startOpts " . implode( ',', $varMap ) . 02578 " FROM $srcTable $useIndex "; 02579 02580 if ( $conds != '*' ) { 02581 if ( is_array( $conds ) ) { 02582 $conds = $this->makeList( $conds, LIST_AND ); 02583 } 02584 $sql .= " WHERE $conds"; 02585 } 02586 02587 $sql .= " $tailOpts"; 02588 02589 return $this->query( $sql, $fname ); 02590 } 02591 02611 public function limitResult( $sql, $limit, $offset = false ) { 02612 if ( !is_numeric( $limit ) ) { 02613 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 02614 } 02615 return "$sql LIMIT " 02616 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 02617 . "{$limit} "; 02618 } 02619 02625 public function unionSupportsOrderAndLimit() { 02626 return true; // True for almost every DB supported 02627 } 02628 02637 public function unionQueries( $sqls, $all ) { 02638 $glue = $all ? ') UNION ALL (' : ') UNION ('; 02639 return '(' . implode( $glue, $sqls ) . ')'; 02640 } 02641 02651 public function conditional( $cond, $trueVal, $falseVal ) { 02652 if ( is_array( $cond ) ) { 02653 $cond = $this->makeList( $cond, LIST_AND ); 02654 } 02655 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 02656 } 02657 02668 public function strreplace( $orig, $old, $new ) { 02669 return "REPLACE({$orig}, {$old}, {$new})"; 02670 } 02671 02678 public function getServerUptime() { 02679 return 0; 02680 } 02681 02688 public function wasDeadlock() { 02689 return false; 02690 } 02691 02698 public function wasLockTimeout() { 02699 return false; 02700 } 02701 02709 public function wasErrorReissuable() { 02710 return false; 02711 } 02712 02719 public function wasReadOnlyError() { 02720 return false; 02721 } 02722 02741 public function deadlockLoop() { 02742 $this->begin( __METHOD__ ); 02743 $args = func_get_args(); 02744 $function = array_shift( $args ); 02745 $oldIgnore = $this->ignoreErrors( true ); 02746 $tries = DEADLOCK_TRIES; 02747 02748 if ( is_array( $function ) ) { 02749 $fname = $function[0]; 02750 } else { 02751 $fname = $function; 02752 } 02753 02754 do { 02755 $retVal = call_user_func_array( $function, $args ); 02756 $error = $this->lastError(); 02757 $errno = $this->lastErrno(); 02758 $sql = $this->lastQuery(); 02759 02760 if ( $errno ) { 02761 if ( $this->wasDeadlock() ) { 02762 # Retry 02763 usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) ); 02764 } else { 02765 $this->reportQueryError( $error, $errno, $sql, $fname ); 02766 } 02767 } 02768 } while ( $this->wasDeadlock() && --$tries > 0 ); 02769 02770 $this->ignoreErrors( $oldIgnore ); 02771 02772 if ( $tries <= 0 ) { 02773 $this->rollback( __METHOD__ ); 02774 $this->reportQueryError( $error, $errno, $sql, $fname ); 02775 return false; 02776 } else { 02777 $this->commit( __METHOD__ ); 02778 return $retVal; 02779 } 02780 } 02781 02793 public function masterPosWait( DBMasterPos $pos, $timeout ) { 02794 wfProfileIn( __METHOD__ ); 02795 02796 if ( !is_null( $this->mFakeSlaveLag ) ) { 02797 $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 ); 02798 02799 if ( $wait > $timeout * 1e6 ) { 02800 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" ); 02801 wfProfileOut( __METHOD__ ); 02802 return -1; 02803 } elseif ( $wait > 0 ) { 02804 wfDebug( "Fake slave waiting $wait us\n" ); 02805 usleep( $wait ); 02806 wfProfileOut( __METHOD__ ); 02807 return 1; 02808 } else { 02809 wfDebug( "Fake slave up to date ($wait us)\n" ); 02810 wfProfileOut( __METHOD__ ); 02811 return 0; 02812 } 02813 } 02814 02815 wfProfileOut( __METHOD__ ); 02816 02817 # Real waits are implemented in the subclass. 02818 return 0; 02819 } 02820 02826 public function getSlavePos() { 02827 if ( !is_null( $this->mFakeSlaveLag ) ) { 02828 $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag ); 02829 wfDebug( __METHOD__ . ": fake slave pos = $pos\n" ); 02830 return $pos; 02831 } else { 02832 # Stub 02833 return false; 02834 } 02835 } 02836 02842 public function getMasterPos() { 02843 if ( $this->mFakeMaster ) { 02844 return new MySQLMasterPos( 'fake', microtime( true ) ); 02845 } else { 02846 return false; 02847 } 02848 } 02849 02860 final public function onTransactionIdle( Closure $callback ) { 02861 if ( $this->mTrxLevel ) { 02862 $this->mTrxIdleCallbacks[] = $callback; 02863 } else { 02864 $callback(); 02865 } 02866 } 02867 02871 protected function runOnTransactionIdleCallbacks() { 02872 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? 02873 02874 $e = null; // last exception 02875 do { // callbacks may add callbacks :) 02876 $callbacks = $this->mTrxIdleCallbacks; 02877 $this->mTrxIdleCallbacks = array(); // recursion guard 02878 foreach ( $callbacks as $callback ) { 02879 try { 02880 $this->clearFlag( DBO_TRX ); // make each query its own transaction 02881 $callback(); 02882 $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() 02883 } catch ( Exception $e ) {} 02884 } 02885 } while ( count( $this->mTrxIdleCallbacks ) ); 02886 02887 if ( $e instanceof Exception ) { 02888 throw $e; // re-throw any last exception 02889 } 02890 } 02891 02897 final public function begin( $fname = 'DatabaseBase::begin' ) { 02898 if ( $this->mTrxLevel ) { // implicit commit 02899 $this->doCommit( $fname ); 02900 $this->runOnTransactionIdleCallbacks(); 02901 } 02902 $this->doBegin( $fname ); 02903 } 02904 02909 protected function doBegin( $fname ) { 02910 $this->query( 'BEGIN', $fname ); 02911 $this->mTrxLevel = 1; 02912 } 02913 02919 final public function commit( $fname = 'DatabaseBase::commit' ) { 02920 $this->doCommit( $fname ); 02921 $this->runOnTransactionIdleCallbacks(); 02922 } 02923 02928 protected function doCommit( $fname ) { 02929 if ( $this->mTrxLevel ) { 02930 $this->query( 'COMMIT', $fname ); 02931 $this->mTrxLevel = 0; 02932 } 02933 } 02934 02941 final public function rollback( $fname = 'DatabaseBase::rollback' ) { 02942 $this->doRollback( $fname ); 02943 $this->mTrxIdleCallbacks = array(); // cancel 02944 } 02945 02950 protected function doRollback( $fname ) { 02951 if ( $this->mTrxLevel ) { 02952 $this->query( 'ROLLBACK', $fname, true ); 02953 $this->mTrxLevel = 0; 02954 } 02955 } 02956 02971 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 02972 $fname = 'DatabaseBase::duplicateTableStructure' ) 02973 { 02974 throw new MWException( 02975 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 02976 } 02977 02984 function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) { 02985 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 02986 } 02987 02999 public function timestamp( $ts = 0 ) { 03000 return wfTimestamp( TS_MW, $ts ); 03001 } 03002 03016 public function timestampOrNull( $ts = null ) { 03017 if ( is_null( $ts ) ) { 03018 return null; 03019 } else { 03020 return $this->timestamp( $ts ); 03021 } 03022 } 03023 03039 public function resultObject( $result ) { 03040 if ( empty( $result ) ) { 03041 return false; 03042 } elseif ( $result instanceof ResultWrapper ) { 03043 return $result; 03044 } elseif ( $result === true ) { 03045 // Successful write query 03046 return $result; 03047 } else { 03048 return new ResultWrapper( $this, $result ); 03049 } 03050 } 03051 03057 public function ping() { 03058 # Stub. Not essential to override. 03059 return true; 03060 } 03061 03071 public function getLag() { 03072 return intval( $this->mFakeSlaveLag ); 03073 } 03074 03080 function maxListLen() { 03081 return 0; 03082 } 03083 03092 public function encodeBlob( $b ) { 03093 return $b; 03094 } 03095 03103 public function decodeBlob( $b ) { 03104 return $b; 03105 } 03106 03117 public function setSessionOptions( array $options ) {} 03118 03132 public function sourceFile( 03133 $filename, $lineCallback = false, $resultCallback = false, $fname = false 03134 ) { 03135 wfSuppressWarnings(); 03136 $fp = fopen( $filename, 'r' ); 03137 wfRestoreWarnings(); 03138 03139 if ( false === $fp ) { 03140 throw new MWException( "Could not open \"{$filename}\".\n" ); 03141 } 03142 03143 if ( !$fname ) { 03144 $fname = __METHOD__ . "( $filename )"; 03145 } 03146 03147 try { 03148 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname ); 03149 } 03150 catch ( MWException $e ) { 03151 fclose( $fp ); 03152 throw $e; 03153 } 03154 03155 fclose( $fp ); 03156 03157 return $error; 03158 } 03159 03168 public function patchPath( $patch ) { 03169 global $IP; 03170 03171 $dbType = $this->getType(); 03172 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 03173 return "$IP/maintenance/$dbType/archives/$patch"; 03174 } else { 03175 return "$IP/maintenance/archives/$patch"; 03176 } 03177 } 03178 03186 public function setSchemaVars( $vars ) { 03187 $this->mSchemaVars = $vars; 03188 } 03189 03203 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 03204 $fname = 'DatabaseBase::sourceStream', $inputCallback = false ) 03205 { 03206 $cmd = ''; 03207 03208 while ( !feof( $fp ) ) { 03209 if ( $lineCallback ) { 03210 call_user_func( $lineCallback ); 03211 } 03212 03213 $line = trim( fgets( $fp ) ); 03214 03215 if ( $line == '' ) { 03216 continue; 03217 } 03218 03219 if ( '-' == $line[0] && '-' == $line[1] ) { 03220 continue; 03221 } 03222 03223 if ( $cmd != '' ) { 03224 $cmd .= ' '; 03225 } 03226 03227 $done = $this->streamStatementEnd( $cmd, $line ); 03228 03229 $cmd .= "$line\n"; 03230 03231 if ( $done || feof( $fp ) ) { 03232 $cmd = $this->replaceVars( $cmd ); 03233 if ( $inputCallback ) { 03234 call_user_func( $inputCallback, $cmd ); 03235 } 03236 $res = $this->query( $cmd, $fname ); 03237 03238 if ( $resultCallback ) { 03239 call_user_func( $resultCallback, $res, $this ); 03240 } 03241 03242 if ( false === $res ) { 03243 $err = $this->lastError(); 03244 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 03245 } 03246 03247 $cmd = ''; 03248 } 03249 } 03250 03251 return true; 03252 } 03253 03261 public function streamStatementEnd( &$sql, &$newLine ) { 03262 if ( $this->delimiter ) { 03263 $prev = $newLine; 03264 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 03265 if ( $newLine != $prev ) { 03266 return true; 03267 } 03268 } 03269 return false; 03270 } 03271 03289 protected function replaceSchemaVars( $ins ) { 03290 $vars = $this->getSchemaVars(); 03291 foreach ( $vars as $var => $value ) { 03292 // replace '{$var}' 03293 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 03294 // replace `{$var}` 03295 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 03296 // replace /*$var*/ 03297 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins ); 03298 } 03299 return $ins; 03300 } 03301 03309 protected function replaceVars( $ins ) { 03310 $ins = $this->replaceSchemaVars( $ins ); 03311 03312 // Table prefixes 03313 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 03314 array( $this, 'tableNameCallback' ), $ins ); 03315 03316 // Index names 03317 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 03318 array( $this, 'indexNameCallback' ), $ins ); 03319 03320 return $ins; 03321 } 03322 03329 protected function getSchemaVars() { 03330 if ( $this->mSchemaVars ) { 03331 return $this->mSchemaVars; 03332 } else { 03333 return $this->getDefaultSchemaVars(); 03334 } 03335 } 03336 03345 protected function getDefaultSchemaVars() { 03346 return array(); 03347 } 03348 03356 protected function tableNameCallback( $matches ) { 03357 return $this->tableName( $matches[1] ); 03358 } 03359 03367 protected function indexNameCallback( $matches ) { 03368 return $this->indexName( $matches[1] ); 03369 } 03370 03379 public function lockIsFree( $lockName, $method ) { 03380 return true; 03381 } 03382 03394 public function lock( $lockName, $method, $timeout = 5 ) { 03395 return true; 03396 } 03397 03408 public function unlock( $lockName, $method ) { 03409 return true; 03410 } 03411 03422 public function lockTables( $read, $write, $method, $lowPriority = true ) { 03423 return true; 03424 } 03425 03433 public function unlockTables( $method ) { 03434 return true; 03435 } 03436 03444 public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) { 03445 if( !$this->tableExists( $tableName, $fName ) ) { 03446 return false; 03447 } 03448 $sql = "DROP TABLE " . $this->tableName( $tableName ); 03449 if( $this->cascadingDeletes() ) { 03450 $sql .= " CASCADE"; 03451 } 03452 return $this->query( $sql, $fName ); 03453 } 03454 03461 public function getSearchEngine() { 03462 return 'SearchEngineDummy'; 03463 } 03464 03472 public function getInfinity() { 03473 return 'infinity'; 03474 } 03475 03482 public function encodeExpiry( $expiry ) { 03483 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 03484 ? $this->getInfinity() 03485 : $this->timestamp( $expiry ); 03486 } 03487 03495 public function decodeExpiry( $expiry, $format = TS_MW ) { 03496 return ( $expiry == '' || $expiry == $this->getInfinity() ) 03497 ? 'infinity' 03498 : wfTimestamp( $format, $expiry ); 03499 } 03500 03510 public function setBigSelects( $value = true ) { 03511 // no-op 03512 } 03513 03517 public function __toString() { 03518 return (string)$this->mConn; 03519 } 03520 03521 public function __destruct() { 03522 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 03523 trigger_error( "Transaction idle callbacks still pending." ); 03524 } 03525 } 03526 }