00001 <?php
00012 define( 'DEADLOCK_TRIES', 4 );
00014 define( 'DEADLOCK_DELAY_MIN', 500000 );
00016 define( 'DEADLOCK_DELAY_MAX', 1500000 );
00017
00022 abstract class DatabaseBase {
00023
00024 #------------------------------------------------------------------------------
00025 # Variables
00026 #------------------------------------------------------------------------------
00027
00028 protected $mLastQuery = '';
00029 protected $mDoneWrites = false;
00030 protected $mPHPError = false;
00031
00032 protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
00033 protected $mOpened = false;
00034
00035 protected $mFailFunction;
00036 protected $mTablePrefix;
00037 protected $mFlags;
00038 protected $mTrxLevel = 0;
00039 protected $mErrorCount = 0;
00040 protected $mLBInfo = array();
00041 protected $mFakeSlaveLag = null, $mFakeMaster = false;
00042 protected $mDefaultBigSelects = null;
00043
00044 #------------------------------------------------------------------------------
00045 # Accessors
00046 #------------------------------------------------------------------------------
00047 # These optionally set a variable and return the previous state
00048
00053 function failFunction( $function = null ) {
00054 return wfSetVar( $this->mFailFunction, $function );
00055 }
00056
00061 function setOutputPage( $out ) {
00062 wfDeprecated( __METHOD__ );
00063 }
00064
00068 function debug( $debug = null ) {
00069 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
00070 }
00071
00076 function bufferResults( $buffer = null ) {
00077 if ( is_null( $buffer ) ) {
00078 return !(bool)( $this->mFlags & DBO_NOBUFFER );
00079 } else {
00080 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
00081 }
00082 }
00083
00091 function ignoreErrors( $ignoreErrors = null ) {
00092 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
00093 }
00094
00099 function trxLevel( $level = null ) {
00100 return wfSetVar( $this->mTrxLevel, $level );
00101 }
00102
00106 function errorCount( $count = null ) {
00107 return wfSetVar( $this->mErrorCount, $count );
00108 }
00109
00110 function tablePrefix( $prefix = null ) {
00111 return wfSetVar( $this->mTablePrefix, $prefix );
00112 }
00113
00117 function getLBInfo( $name = null ) {
00118 if ( is_null( $name ) ) {
00119 return $this->mLBInfo;
00120 } else {
00121 if ( array_key_exists( $name, $this->mLBInfo ) ) {
00122 return $this->mLBInfo[$name];
00123 } else {
00124 return null;
00125 }
00126 }
00127 }
00128
00129 function setLBInfo( $name, $value = null ) {
00130 if ( is_null( $value ) ) {
00131 $this->mLBInfo = $name;
00132 } else {
00133 $this->mLBInfo[$name] = $value;
00134 }
00135 }
00136
00140 function setFakeSlaveLag( $lag ) {
00141 $this->mFakeSlaveLag = $lag;
00142 }
00143
00147 function setFakeMaster( $enabled = true ) {
00148 $this->mFakeMaster = $enabled;
00149 }
00150
00154 function cascadingDeletes() {
00155 return false;
00156 }
00157
00161 function cleanupTriggers() {
00162 return false;
00163 }
00164
00169 function strictIPs() {
00170 return false;
00171 }
00172
00176 function realTimestamps() {
00177 return false;
00178 }
00179
00183 function implicitGroupby() {
00184 return true;
00185 }
00186
00191 function implicitOrderby() {
00192 return true;
00193 }
00194
00199 function standardSelectDistinct() {
00200 return true;
00201 }
00202
00207 function searchableIPs() {
00208 return false;
00209 }
00210
00214 function functionalIndexes() {
00215 return false;
00216 }
00217
00222 function lastQuery() { return $this->mLastQuery; }
00223
00224
00229 function doneWrites() { return $this->mDoneWrites; }
00230
00235 function isOpen() { return $this->mOpened; }
00236
00249 function setFlag( $flag ) {
00250 $this->mFlags |= $flag;
00251 }
00252
00258 function clearFlag( $flag ) {
00259 $this->mFlags &= ~$flag;
00260 }
00261
00268 function getFlag( $flag ) {
00269 return !!($this->mFlags & $flag);
00270 }
00271
00275 function getProperty( $name ) {
00276 return $this->$name;
00277 }
00278
00279 function getWikiID() {
00280 if( $this->mTablePrefix ) {
00281 return "{$this->mDBname}-{$this->mTablePrefix}";
00282 } else {
00283 return $this->mDBname;
00284 }
00285 }
00286
00290 abstract function getType();
00291
00292 #------------------------------------------------------------------------------
00293 # Other functions
00294 #------------------------------------------------------------------------------
00295
00306 function __construct( $server = false, $user = false, $password = false, $dbName = false,
00307 $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) {
00308
00309 global $wgOut, $wgDBprefix, $wgCommandLineMode;
00310 # Can't get a reference if it hasn't been set yet
00311 if ( !isset( $wgOut ) ) {
00312 $wgOut = null;
00313 }
00314
00315 $this->mFailFunction = $failFunction;
00316 $this->mFlags = $flags;
00317
00318 if ( $this->mFlags & DBO_DEFAULT ) {
00319 if ( $wgCommandLineMode ) {
00320 $this->mFlags &= ~DBO_TRX;
00321 } else {
00322 $this->mFlags |= DBO_TRX;
00323 }
00324 }
00325
00326
00327
00328
00329
00330
00331
00332
00334 if ( $tablePrefix == 'get from global' ) {
00335 $this->mTablePrefix = $wgDBprefix;
00336 } else {
00337 $this->mTablePrefix = $tablePrefix;
00338 }
00339
00340 if ( $server ) {
00341 $this->open( $server, $user, $password, $dbName );
00342 }
00343 }
00344
00354 static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
00355 {
00356 return new DatabaseMysql( $server, $user, $password, $dbName, $failFunction, $flags );
00357 }
00358
00367 abstract function open( $server, $user, $password, $dbName );
00368
00369 protected function installErrorHandler() {
00370 $this->mPHPError = false;
00371 $this->htmlErrors = ini_set( 'html_errors', '0' );
00372 set_error_handler( array( $this, 'connectionErrorHandler' ) );
00373 }
00374
00375 protected function restoreErrorHandler() {
00376 restore_error_handler();
00377 if ( $this->htmlErrors !== false ) {
00378 ini_set( 'html_errors', $this->htmlErrors );
00379 }
00380 if ( $this->mPHPError ) {
00381 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
00382 $error = preg_replace( '!^.*?:(.*)$!', '$1', $error );
00383 return $error;
00384 } else {
00385 return false;
00386 }
00387 }
00388
00389 protected function connectionErrorHandler( $errno, $errstr ) {
00390 $this->mPHPError = $errstr;
00391 }
00392
00399 function close() {
00400 # Stub, should probably be overridden
00401 return true;
00402 }
00403
00407 function reportConnectionError( $error = 'Unknown error' ) {
00408 $myError = $this->lastError();
00409 if ( $myError ) {
00410 $error = $myError;
00411 }
00412
00413 if ( $this->mFailFunction ) {
00414 # Legacy error handling method
00415 if ( !is_int( $this->mFailFunction ) ) {
00416 $ff = $this->mFailFunction;
00417 $ff( $this, $error );
00418 }
00419 } else {
00420 # New method
00421 throw new DBConnectionError( $this, $error );
00422 }
00423 }
00424
00429 function isWriteQuery( $sql ) {
00430 return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
00431 }
00432
00445 public function query( $sql, $fname = '', $tempIgnore = false ) {
00446 global $wgProfiler;
00447
00448 $isMaster = !is_null( $this->getLBInfo( 'master' ) );
00449 if ( isset( $wgProfiler ) ) {
00450 # generalizeSQL will probably cut down the query to reasonable
00451 # logging size most of the time. The substr is really just a sanity check.
00452
00453 # Who's been wasting my precious column space? -- TS
00454 #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
00455
00456 if ( $isMaster ) {
00457 $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
00458 $totalProf = 'Database::query-master';
00459 } else {
00460 $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
00461 $totalProf = 'Database::query';
00462 }
00463 wfProfileIn( $totalProf );
00464 wfProfileIn( $queryProf );
00465 }
00466
00467 $this->mLastQuery = $sql;
00468 if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
00469
00470 wfDebug( __METHOD__.": Writes done: $sql\n" );
00471 $this->mDoneWrites = true;
00472 }
00473
00474 # Add a comment for easy SHOW PROCESSLIST interpretation
00475 #if ( $fname ) {
00476 global $wgUser;
00477 if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) {
00478 $userName = $wgUser->getName();
00479 if ( mb_strlen( $userName ) > 15 ) {
00480 $userName = mb_substr( $userName, 0, 15 ) . '...';
00481 }
00482 $userName = str_replace( '/', '', $userName );
00483 } else {
00484 $userName = '';
00485 }
00486 $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1);
00487 #} else {
00488 # $commentedSql = $sql;
00489 #}
00490
00491 # If DBO_TRX is set, start a transaction
00492 if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
00493 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') {
00494
00495
00496
00497 $sqlstart = substr($sql,0,10);
00498 if (strpos($sqlstart,"SHOW ")!==0 and strpos($sqlstart,"SET ")!==0)
00499 $this->begin();
00500 }
00501
00502 if ( $this->debug() ) {
00503 $sqlx = substr( $commentedSql, 0, 500 );
00504 $sqlx = strtr( $sqlx, "\t\n", ' ' );
00505 if ( $isMaster ) {
00506 wfDebug( "SQL-master: $sqlx\n" );
00507 } else {
00508 wfDebug( "SQL: $sqlx\n" );
00509 }
00510 }
00511
00512 if ( istainted( $sql ) & TC_MYSQL ) {
00513 throw new MWException( 'Tainted query found' );
00514 }
00515
00516 # Do the query and handle errors
00517 $ret = $this->doQuery( $commentedSql );
00518
00519 # Try reconnecting if the connection was lost
00520 if ( false === $ret && $this->wasErrorReissuable() ) {
00521 # Transaction is gone, like it or not
00522 $this->mTrxLevel = 0;
00523 wfDebug( "Connection lost, reconnecting...\n" );
00524 if ( $this->ping() ) {
00525 wfDebug( "Reconnected\n" );
00526 $sqlx = substr( $commentedSql, 0, 500 );
00527 $sqlx = strtr( $sqlx, "\t\n", ' ' );
00528 global $wgRequestTime;
00529 $elapsed = round( microtime(true) - $wgRequestTime, 3 );
00530 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
00531 $ret = $this->doQuery( $commentedSql );
00532 } else {
00533 wfDebug( "Failed\n" );
00534 }
00535 }
00536
00537 if ( false === $ret ) {
00538 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
00539 }
00540
00541 if ( isset( $wgProfiler ) ) {
00542 wfProfileOut( $queryProf );
00543 wfProfileOut( $totalProf );
00544 }
00545 return $this->resultObject( $ret );
00546 }
00547
00554 abstract function doQuery( $sql );
00555
00563 function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
00564 global $wgCommandLineMode;
00565 # Ignore errors during error handling to avoid infinite recursion
00566 $ignore = $this->ignoreErrors( true );
00567 ++$this->mErrorCount;
00568
00569 if( $ignore || $tempIgnore ) {
00570 wfDebug("SQL ERROR (ignored): $error\n");
00571 $this->ignoreErrors( $ignore );
00572 } else {
00573 $sql1line = str_replace( "\n", "\\n", $sql );
00574 wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n");
00575 wfDebug("SQL ERROR: " . $error . "\n");
00576 throw new DBQueryError( $this, $error, $errno, $sql, $fname );
00577 }
00578 }
00579
00580
00590 function prepare( $sql, $func = 'Database::prepare' ) {
00591
00592
00593
00594 return array( 'query' => $sql, 'func' => $func );
00595 }
00596
00597 function freePrepared( $prepared ) {
00598
00599 }
00600
00606 function execute( $prepared, $args = null ) {
00607 if( !is_array( $args ) ) {
00608 # Pull the var args
00609 $args = func_get_args();
00610 array_shift( $args );
00611 }
00612 $sql = $this->fillPrepared( $prepared['query'], $args );
00613 return $this->query( $sql, $prepared['func'] );
00614 }
00615
00622 function safeQuery( $query, $args = null ) {
00623 $prepared = $this->prepare( $query, 'Database::safeQuery' );
00624 if( !is_array( $args ) ) {
00625 # Pull the var args
00626 $args = func_get_args();
00627 array_shift( $args );
00628 }
00629 $retval = $this->execute( $prepared, $args );
00630 $this->freePrepared( $prepared );
00631 return $retval;
00632 }
00633
00641 function fillPrepared( $preparedQuery, $args ) {
00642 reset( $args );
00643 $this->preparedArgs =& $args;
00644 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
00645 array( &$this, 'fillPreparedArg' ), $preparedQuery );
00646 }
00647
00657 function fillPreparedArg( $matches ) {
00658 switch( $matches[1] ) {
00659 case '\\?': return '?';
00660 case '\\!': return '!';
00661 case '\\&': return '&';
00662 }
00663 list( , $arg ) = each( $this->preparedArgs );
00664 switch( $matches[1] ) {
00665 case '?': return $this->addQuotes( $arg );
00666 case '!': return $arg;
00667 case '&':
00668 # return $this->addQuotes( file_get_contents( $arg ) );
00669 throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
00670 default:
00671 throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
00672 }
00673 }
00674
00679 function freeResult( $res ) {
00680 # Stub. Might not really need to be overridden, since results should
00681 # be freed by PHP when the variable goes out of scope anyway.
00682 }
00683
00693 abstract function fetchObject( $res );
00694
00703 abstract function fetchRow( $res );
00704
00709 abstract function numRows( $res );
00710
00716 abstract function numFields( $res );
00717
00725 abstract function fieldName( $res, $n );
00726
00737 abstract function insertId();
00738
00745 abstract function dataSeek( $res, $row );
00746
00751 abstract function lastErrno();
00752
00757 abstract function lastError();
00758
00763 abstract function affectedRows();
00764
00773 function set( $table, $var, $value, $cond, $fname = 'Database::set' ) {
00774 $table = $this->tableName( $table );
00775 $sql = "UPDATE $table SET $var = '" .
00776 $this->strencode( $value ) . "' WHERE ($cond)";
00777 return (bool)$this->query( $sql, $fname );
00778 }
00779
00785 function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
00786 if ( !is_array( $options ) ) {
00787 $options = array( $options );
00788 }
00789 $options['LIMIT'] = 1;
00790
00791 $res = $this->select( $table, $var, $cond, $fname, $options );
00792 if ( $res === false || !$this->numRows( $res ) ) {
00793 return false;
00794 }
00795 $row = $this->fetchRow( $res );
00796 if ( $row !== false ) {
00797 $this->freeResult( $res );
00798 return reset( $row );
00799 } else {
00800 return false;
00801 }
00802 }
00803
00814 function makeSelectOptions( $options ) {
00815 $preLimitTail = $postLimitTail = '';
00816 $startOpts = '';
00817
00818 $noKeyOptions = array();
00819 foreach ( $options as $key => $option ) {
00820 if ( is_numeric( $key ) ) {
00821 $noKeyOptions[$option] = true;
00822 }
00823 }
00824
00825 if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
00826 if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
00827 if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
00828
00829
00830
00831
00832
00833
00834
00835 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
00836 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
00837 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
00838
00839 # Various MySQL extensions
00840 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
00841 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
00842 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
00843 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
00844 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
00845 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
00846 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
00847 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
00848
00849 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
00850 $useIndex = $this->useIndexClause( $options['USE INDEX'] );
00851 } else {
00852 $useIndex = '';
00853 }
00854
00855 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
00856 }
00857
00871 function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() )
00872 {
00873 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
00874 return $this->query( $sql, $fname );
00875 }
00876
00890 function selectSQLText( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() ) {
00891 if( is_array( $vars ) ) {
00892 $vars = implode( ',', $vars );
00893 }
00894 if( !is_array( $options ) ) {
00895 $options = array( $options );
00896 }
00897 if( is_array( $table ) ) {
00898 if ( !empty($join_conds) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) )
00899 $from = ' FROM ' . $this->tableNamesWithUseIndexOrJOIN( $table, @$options['USE INDEX'], $join_conds );
00900 else
00901 $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
00902 } elseif ($table!='') {
00903 if ($table{0}==' ') {
00904 $from = ' FROM ' . $table;
00905 } else {
00906 $from = ' FROM ' . $this->tableName( $table );
00907 }
00908 } else {
00909 $from = '';
00910 }
00911
00912 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
00913
00914 if( !empty( $conds ) ) {
00915 if ( is_array( $conds ) ) {
00916 $conds = $this->makeList( $conds, LIST_AND );
00917 }
00918 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
00919 } else {
00920 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
00921 }
00922
00923 if (isset($options['LIMIT']))
00924 $sql = $this->limitResult($sql, $options['LIMIT'],
00925 isset($options['OFFSET']) ? $options['OFFSET'] : false);
00926 $sql = "$sql $postLimitTail";
00927
00928 if (isset($options['EXPLAIN'])) {
00929 $sql = 'EXPLAIN ' . $sql;
00930 }
00931 return $sql;
00932 }
00933
00952 function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array(), $join_conds = array() ) {
00953 $options['LIMIT'] = 1;
00954 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
00955 if ( $res === false )
00956 return false;
00957 if ( !$this->numRows($res) ) {
00958 $this->freeResult($res);
00959 return false;
00960 }
00961 $obj = $this->fetchObject( $res );
00962 $this->freeResult( $res );
00963 return $obj;
00964
00965 }
00966
00980 public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
00981 $rows = 0;
00982 $res = $this->select ( $table, 'COUNT(*) AS rowcount', $conds, $fname, $options );
00983 if ( $res ) {
00984 $row = $this->fetchRow( $res );
00985 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
00986 }
00987 $this->freeResult( $res );
00988 return $rows;
00989 }
00990
00997 static function generalizeSQL( $sql ) {
00998 # This does the same as the regexp below would do, but in such a way
00999 # as to avoid crashing php on some large strings.
01000 # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
01001
01002 $sql = str_replace ( "\\\\", '', $sql);
01003 $sql = str_replace ( "\\'", '', $sql);
01004 $sql = str_replace ( "\\\"", '', $sql);
01005 $sql = preg_replace ("/'.*'/s", "'X'", $sql);
01006 $sql = preg_replace ('/".*"/s', "'X'", $sql);
01007
01008 # All newlines, tabs, etc replaced by single space
01009 $sql = preg_replace ( '/\s+/', ' ', $sql);
01010
01011 # All numbers => N
01012 $sql = preg_replace ('/-?[0-9]+/s', 'N', $sql);
01013
01014 return $sql;
01015 }
01016
01022 function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
01023 $table = $this->tableName( $table );
01024 $res = $this->query( 'DESCRIBE '.$table, $fname );
01025 if ( !$res ) {
01026 return null;
01027 }
01028
01029 $found = false;
01030
01031 while ( $row = $this->fetchObject( $res ) ) {
01032 if ( $row->Field == $field ) {
01033 $found = true;
01034 break;
01035 }
01036 }
01037 return $found;
01038 }
01039
01045 function indexExists( $table, $index, $fname = 'Database::indexExists' ) {
01046 $info = $this->indexInfo( $table, $index, $fname );
01047 if ( is_null( $info ) ) {
01048 return null;
01049 } else {
01050 return $info !== false;
01051 }
01052 }
01053
01054
01059 function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
01060 # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
01061 # SHOW INDEX should work for 3.x and up:
01062 # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
01063 $table = $this->tableName( $table );
01064 $index = $this->indexName( $index );
01065 $sql = 'SHOW INDEX FROM '.$table;
01066 $res = $this->query( $sql, $fname );
01067 if ( !$res ) {
01068 return null;
01069 }
01070
01071 $result = array();
01072 while ( $row = $this->fetchObject( $res ) ) {
01073 if ( $row->Key_name == $index ) {
01074 $result[] = $row;
01075 }
01076 }
01077 $this->freeResult($res);
01078
01079 return empty($result) ? false : $result;
01080 }
01081
01085 function tableExists( $table ) {
01086 $table = $this->tableName( $table );
01087 $old = $this->ignoreErrors( true );
01088 $res = $this->query( "SELECT 1 FROM $table LIMIT 1" );
01089 $this->ignoreErrors( $old );
01090 if( $res ) {
01091 $this->freeResult( $res );
01092 return true;
01093 } else {
01094 return false;
01095 }
01096 }
01097
01105 abstract function fieldInfo( $table, $field );
01106
01110 function fieldType( $res, $index ) {
01111 if ( $res instanceof ResultWrapper ) {
01112 $res = $res->result;
01113 }
01114 return mysql_field_type( $res, $index );
01115 }
01116
01120 function indexUnique( $table, $index ) {
01121 $indexInfo = $this->indexInfo( $table, $index );
01122 if ( !$indexInfo ) {
01123 return null;
01124 }
01125 return !$indexInfo[0]->Non_unique;
01126 }
01127
01137 function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
01138 # No rows to insert, easy just return now
01139 if ( !count( $a ) ) {
01140 return true;
01141 }
01142
01143 $table = $this->tableName( $table );
01144 if ( !is_array( $options ) ) {
01145 $options = array( $options );
01146 }
01147 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
01148 $multi = true;
01149 $keys = array_keys( $a[0] );
01150 } else {
01151 $multi = false;
01152 $keys = array_keys( $a );
01153 }
01154
01155 $sql = 'INSERT ' . implode( ' ', $options ) .
01156 " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
01157
01158 if ( $multi ) {
01159 $first = true;
01160 foreach ( $a as $row ) {
01161 if ( $first ) {
01162 $first = false;
01163 } else {
01164 $sql .= ',';
01165 }
01166 $sql .= '(' . $this->makeList( $row ) . ')';
01167 }
01168 } else {
01169 $sql .= '(' . $this->makeList( $a ) . ')';
01170 }
01171 return (bool)$this->query( $sql, $fname );
01172 }
01173
01181 function makeUpdateOptions( $options ) {
01182 if( !is_array( $options ) ) {
01183 $options = array( $options );
01184 }
01185 $opts = array();
01186 if ( in_array( 'LOW_PRIORITY', $options ) )
01187 $opts[] = $this->lowPriorityOption();
01188 if ( in_array( 'IGNORE', $options ) )
01189 $opts[] = 'IGNORE';
01190 return implode(' ', $opts);
01191 }
01192
01205 function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
01206 $table = $this->tableName( $table );
01207 $opts = $this->makeUpdateOptions( $options );
01208 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
01209 if ( $conds != '*' ) {
01210 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
01211 }
01212 return $this->query( $sql, $fname );
01213 }
01214
01224 function makeList( $a, $mode = LIST_COMMA ) {
01225 if ( !is_array( $a ) ) {
01226 throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
01227 }
01228
01229 $first = true;
01230 $list = '';
01231 foreach ( $a as $field => $value ) {
01232 if ( !$first ) {
01233 if ( $mode == LIST_AND ) {
01234 $list .= ' AND ';
01235 } elseif($mode == LIST_OR) {
01236 $list .= ' OR ';
01237 } else {
01238 $list .= ',';
01239 }
01240 } else {
01241 $first = false;
01242 }
01243 if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
01244 $list .= "($value)";
01245 } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
01246 $list .= "$value";
01247 } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
01248 if( count( $value ) == 0 ) {
01249 throw new MWException( __METHOD__.': empty input' );
01250 } elseif( count( $value ) == 1 ) {
01251
01252
01253
01254 $value = array_values( $value );
01255 $list .= $field." = ".$this->addQuotes( $value[0] );
01256 } else {
01257 $list .= $field." IN (".$this->makeList($value).") ";
01258 }
01259 } elseif( $value === null ) {
01260 if ( $mode == LIST_AND || $mode == LIST_OR ) {
01261 $list .= "$field IS ";
01262 } elseif ( $mode == LIST_SET ) {
01263 $list .= "$field = ";
01264 }
01265 $list .= 'NULL';
01266 } else {
01267 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
01268 $list .= "$field = ";
01269 }
01270 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
01271 }
01272 }
01273 return $list;
01274 }
01275
01280 function bitNot($field) {
01281 return "(~$bitField)";
01282 }
01283
01284 function bitAnd($fieldLeft, $fieldRight) {
01285 return "($fieldLeft & $fieldRight)";
01286 }
01287
01288 function bitOr($fieldLeft, $fieldRight) {
01289 return "($fieldLeft | $fieldRight)";
01290 }
01291
01297 function selectDB( $db ) {
01298 # Stub. Shouldn't cause serious problems if it's not overridden, but
01299 # if your database engine supports a concept similar to MySQL's
01300 # databases you may as well. TODO: explain what exactly will fail if
01301 # this is not overridden.
01302 return true;
01303 }
01304
01308 function getDBname() {
01309 return $this->mDBname;
01310 }
01311
01315 function getServer() {
01316 return $this->mServer;
01317 }
01318
01332 function tableName( $name ) {
01333 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
01334 # Skip the entire process when we have a string quoted on both ends.
01335 # Note that we check the end so that we will still quote any use of
01336 # use of `database`.table. But won't break things if someone wants
01337 # to query a database table with a dot in the name.
01338 if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) return $name;
01339
01340 # Lets test for any bits of text that should never show up in a table
01341 # name. Basically anything like JOIN or ON which are actually part of
01342 # SQL queries, but may end up inside of the table value to combine
01343 # sql. Such as how the API is doing.
01344 # Note that we use a whitespace test rather than a \b test to avoid
01345 # any remote case where a word like on may be inside of a table name
01346 # surrounded by symbols which may be considered word breaks.
01347 if( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) return $name;
01348
01349 # Split database and table into proper variables.
01350 # We reverse the explode so that database.table and table both output
01351 # the correct table.
01352 $dbDetails = array_reverse( explode( '.', $name, 2 ) );
01353 if( isset( $dbDetails[1] ) ) @list( $table, $database ) = $dbDetails;
01354 else @list( $table ) = $dbDetails;
01355 $prefix = $this->mTablePrefix; # Default prefix
01356
01357 # A database name has been specified in input. Quote the table name
01358 # because we don't want any prefixes added.
01359 if( isset($database) ) $table = ( $table[0] == '`' ? $table : "`{$table}`" );
01360
01361 # Note that we use the long format because php will complain in in_array if
01362 # the input is not an array, and will complain in is_array if it is not set.
01363 if( !isset( $database ) # Don't use shared database if pre selected.
01364 && isset( $wgSharedDB ) # We have a shared database
01365 && $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
01366 && isset( $wgSharedTables )
01367 && is_array( $wgSharedTables )
01368 && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
01369 $database = $wgSharedDB;
01370 $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
01371 }
01372
01373 # Quote the $database and $table and apply the prefix if not quoted.
01374 if( isset($database) ) $database = ( $database[0] == '`' ? $database : "`{$database}`" );
01375 $table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
01376
01377 # Merge our database and table into our final table name.
01378 $tableName = ( isset($database) ? "{$database}.{$table}" : "{$table}" );
01379
01380 # We're finished, return.
01381 return $tableName;
01382 }
01383
01393 public function tableNames() {
01394 $inArray = func_get_args();
01395 $retVal = array();
01396 foreach ( $inArray as $name ) {
01397 $retVal[$name] = $this->tableName( $name );
01398 }
01399 return $retVal;
01400 }
01401
01411 public function tableNamesN() {
01412 $inArray = func_get_args();
01413 $retVal = array();
01414 foreach ( $inArray as $name ) {
01415 $retVal[] = $this->tableName( $name );
01416 }
01417 return $retVal;
01418 }
01419
01423 function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
01424 $ret = array();
01425 $retJOIN = array();
01426 $use_index_safe = is_array($use_index) ? $use_index : array();
01427 $join_conds_safe = is_array($join_conds) ? $join_conds : array();
01428 foreach ( $tables as $table ) {
01429
01430 if ( isset($join_conds_safe[$table]) && isset($use_index_safe[$table]) ) {
01431 $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
01432 $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
01433 $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
01434 $retJOIN[] = $tableClause;
01435
01436 } else if ( isset($use_index_safe[$table]) ) {
01437 $tableClause = $this->tableName( $table );
01438 $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
01439 $ret[] = $tableClause;
01440
01441 } else if ( isset($join_conds_safe[$table]) ) {
01442 $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
01443 $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
01444 $retJOIN[] = $tableClause;
01445 } else {
01446 $tableClause = $this->tableName( $table );
01447 $ret[] = $tableClause;
01448 }
01449 }
01450
01451 $straightJoins = !empty($ret) ? implode( ',', $ret ) : "";
01452 $otherJoins = !empty($retJOIN) ? implode( ' ', $retJOIN ) : "";
01453
01454 return implode(' ',array($straightJoins,$otherJoins) );
01455 }
01456
01460 function indexName( $index ) {
01461
01462 $renamed = array(
01463 'ar_usertext_timestamp' => 'usertext_timestamp',
01464 'un_user_id' => 'user_id',
01465 'un_user_ip' => 'user_ip',
01466 );
01467 if( isset( $renamed[$index] ) ) {
01468 return $renamed[$index];
01469 } else {
01470 return $index;
01471 }
01472 }
01473
01479 abstract function strencode( $s );
01480
01485 function addQuotes( $s ) {
01486 if ( $s === null ) {
01487 return 'NULL';
01488 } else {
01489 # This will also quote numeric values. This should be harmless,
01490 # and protects against weird problems that occur when they really
01491 # _are_ strings such as article titles and string->number->string
01492 # conversion is not 1:1.
01493 return "'" . $this->strencode( $s ) . "'";
01494 }
01495 }
01496
01502 function escapeLike( $s ) {
01503 $s = str_replace( '\\', '\\\\', $s );
01504 $s = $this->strencode( $s );
01505 $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
01506 return $s;
01507 }
01508
01520 function buildLike() {
01521 $params = func_get_args();
01522 if (count($params) > 0 && is_array($params[0])) {
01523 $params = $params[0];
01524 }
01525
01526 $s = '';
01527 foreach( $params as $value) {
01528 if( $value instanceof LikeMatch ) {
01529 $s .= $value->toString();
01530 } else {
01531 $s .= $this->escapeLike( $value );
01532 }
01533 }
01534 return " LIKE '" . $s . "' ";
01535 }
01536
01540 function anyChar() {
01541 return new LikeMatch( '_' );
01542 }
01543
01547 function anyString() {
01548 return new LikeMatch( '%' );
01549 }
01550
01556 function nextSequenceValue( $seqName ) {
01557 return null;
01558 }
01559
01568 function useIndexClause( $index ) {
01569 return '';
01570 }
01571
01585 function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
01586 $table = $this->tableName( $table );
01587
01588 # Single row case
01589 if ( !is_array( reset( $rows ) ) ) {
01590 $rows = array( $rows );
01591 }
01592
01593 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
01594 $first = true;
01595 foreach ( $rows as $row ) {
01596 if ( $first ) {
01597 $first = false;
01598 } else {
01599 $sql .= ',';
01600 }
01601 $sql .= '(' . $this->makeList( $row ) . ')';
01602 }
01603 return $this->query( $sql, $fname );
01604 }
01605
01622 function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
01623 if ( !$conds ) {
01624 throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
01625 }
01626
01627 $delTable = $this->tableName( $delTable );
01628 $joinTable = $this->tableName( $joinTable );
01629 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
01630 if ( $conds != '*' ) {
01631 $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
01632 }
01633
01634 return $this->query( $sql, $fname );
01635 }
01636
01640 function textFieldSize( $table, $field ) {
01641 $table = $this->tableName( $table );
01642 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
01643 $res = $this->query( $sql, 'Database::textFieldSize' );
01644 $row = $this->fetchObject( $res );
01645 $this->freeResult( $res );
01646
01647 $m = array();
01648 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
01649 $size = $m[1];
01650 } else {
01651 $size = -1;
01652 }
01653 return $size;
01654 }
01655
01663 function lowPriorityOption() {
01664 return '';
01665 }
01666
01672 function delete( $table, $conds, $fname = 'Database::delete' ) {
01673 if ( !$conds ) {
01674 throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
01675 }
01676 $table = $this->tableName( $table );
01677 $sql = "DELETE FROM $table";
01678 if ( $conds != '*' ) {
01679 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
01680 }
01681 return $this->query( $sql, $fname );
01682 }
01683
01691 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
01692 $insertOptions = array(), $selectOptions = array() )
01693 {
01694 $destTable = $this->tableName( $destTable );
01695 if ( is_array( $insertOptions ) ) {
01696 $insertOptions = implode( ' ', $insertOptions );
01697 }
01698 if( !is_array( $selectOptions ) ) {
01699 $selectOptions = array( $selectOptions );
01700 }
01701 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
01702 if( is_array( $srcTable ) ) {
01703 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
01704 } else {
01705 $srcTable = $this->tableName( $srcTable );
01706 }
01707 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
01708 " SELECT $startOpts " . implode( ',', $varMap ) .
01709 " FROM $srcTable $useIndex ";
01710 if ( $conds != '*' ) {
01711 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
01712 }
01713 $sql .= " $tailOpts";
01714 return $this->query( $sql, $fname );
01715 }
01716
01735 function limitResult( $sql, $limit, $offset=false ) {
01736 if( !is_numeric( $limit ) ) {
01737 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
01738 }
01739 return "$sql LIMIT "
01740 . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" )
01741 . "{$limit} ";
01742 }
01743 function limitResultForUpdate( $sql, $num ) {
01744 return $this->limitResult( $sql, $num, 0 );
01745 }
01746
01752 function unionSupportsOrderAndLimit() {
01753 return true;
01754 }
01755
01764 function unionQueries($sqls, $all) {
01765 $glue = $all ? ') UNION ALL (' : ') UNION (';
01766 return '('.implode( $glue, $sqls ) . ')';
01767 }
01768
01778 function conditional( $cond, $trueVal, $falseVal ) {
01779 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
01780 }
01781
01790 function strreplace( $orig, $old, $new ) {
01791 return "REPLACE({$orig}, {$old}, {$new})";
01792 }
01793
01798 function wasDeadlock() {
01799 return false;
01800 }
01801
01807 function wasErrorReissuable() {
01808 return false;
01809 }
01810
01815 function wasReadOnlyError() {
01816 return false;
01817 }
01818
01835 function deadlockLoop() {
01836 $myFname = 'Database::deadlockLoop';
01837
01838 $this->begin();
01839 $args = func_get_args();
01840 $function = array_shift( $args );
01841 $oldIgnore = $this->ignoreErrors( true );
01842 $tries = DEADLOCK_TRIES;
01843 if ( is_array( $function ) ) {
01844 $fname = $function[0];
01845 } else {
01846 $fname = $function;
01847 }
01848 do {
01849 $retVal = call_user_func_array( $function, $args );
01850 $error = $this->lastError();
01851 $errno = $this->lastErrno();
01852 $sql = $this->lastQuery();
01853
01854 if ( $errno ) {
01855 if ( $this->wasDeadlock() ) {
01856 # Retry
01857 usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
01858 } else {
01859 $this->reportQueryError( $error, $errno, $sql, $fname );
01860 }
01861 }
01862 } while( $this->wasDeadlock() && --$tries > 0 );
01863 $this->ignoreErrors( $oldIgnore );
01864 if ( $tries <= 0 ) {
01865 $this->query( 'ROLLBACK', $myFname );
01866 $this->reportQueryError( $error, $errno, $sql, $fname );
01867 return false;
01868 } else {
01869 $this->query( 'COMMIT', $myFname );
01870 return $retVal;
01871 }
01872 }
01873
01880 function masterPosWait( MySQLMasterPos $pos, $timeout ) {
01881 $fname = 'Database::masterPosWait';
01882 wfProfileIn( $fname );
01883
01884 # Commit any open transactions
01885 if ( $this->mTrxLevel ) {
01886 $this->commit();
01887 }
01888
01889 if ( !is_null( $this->mFakeSlaveLag ) ) {
01890 $wait = intval( ( $pos->pos - microtime(true) + $this->mFakeSlaveLag ) * 1e6 );
01891 if ( $wait > $timeout * 1e6 ) {
01892 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
01893 wfProfileOut( $fname );
01894 return -1;
01895 } elseif ( $wait > 0 ) {
01896 wfDebug( "Fake slave waiting $wait us\n" );
01897 usleep( $wait );
01898 wfProfileOut( $fname );
01899 return 1;
01900 } else {
01901 wfDebug( "Fake slave up to date ($wait us)\n" );
01902 wfProfileOut( $fname );
01903 return 0;
01904 }
01905 }
01906
01907 # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
01908 $encFile = $this->addQuotes( $pos->file );
01909 $encPos = intval( $pos->pos );
01910 $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
01911 $res = $this->doQuery( $sql );
01912 if ( $res && $row = $this->fetchRow( $res ) ) {
01913 $this->freeResult( $res );
01914 wfProfileOut( $fname );
01915 return $row[0];
01916 } else {
01917 wfProfileOut( $fname );
01918 return false;
01919 }
01920 }
01921
01925 function getSlavePos() {
01926 if ( !is_null( $this->mFakeSlaveLag ) ) {
01927 $pos = new MySQLMasterPos( 'fake', microtime(true) - $this->mFakeSlaveLag );
01928 wfDebug( __METHOD__.": fake slave pos = $pos\n" );
01929 return $pos;
01930 }
01931 $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
01932 $row = $this->fetchObject( $res );
01933 if ( $row ) {
01934 $pos = isset($row->Exec_master_log_pos) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
01935 return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
01936 } else {
01937 return false;
01938 }
01939 }
01940
01944 function getMasterPos() {
01945 if ( $this->mFakeMaster ) {
01946 return new MySQLMasterPos( 'fake', microtime( true ) );
01947 }
01948 $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
01949 $row = $this->fetchObject( $res );
01950 if ( $row ) {
01951 return new MySQLMasterPos( $row->File, $row->Position );
01952 } else {
01953 return false;
01954 }
01955 }
01956
01960 function begin( $fname = 'Database::begin' ) {
01961 $this->query( 'BEGIN', $fname );
01962 $this->mTrxLevel = 1;
01963 }
01964
01968 function commit( $fname = 'Database::commit' ) {
01969 $this->query( 'COMMIT', $fname );
01970 $this->mTrxLevel = 0;
01971 }
01972
01977 function rollback( $fname = 'Database::rollback' ) {
01978 $this->query( 'ROLLBACK', $fname, true );
01979 $this->mTrxLevel = 0;
01980 }
01981
01986 function immediateBegin( $fname = 'Database::immediateBegin' ) {
01987 $this->begin();
01988 }
01989
01994 function immediateCommit( $fname = 'Database::immediateCommit' ) {
01995 $this->commit();
01996 }
01997
02009 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'Database::duplicateTableStructure' ) {
02010 throw new MWException( 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
02011 }
02012
02016 function timestamp( $ts=0 ) {
02017 return wfTimestamp(TS_MW,$ts);
02018 }
02019
02023 function timestampOrNull( $ts = null ) {
02024 if( is_null( $ts ) ) {
02025 return null;
02026 } else {
02027 return $this->timestamp( $ts );
02028 }
02029 }
02030
02034 function resultObject( $result ) {
02035 if( empty( $result ) ) {
02036 return false;
02037 } elseif ( $result instanceof ResultWrapper ) {
02038 return $result;
02039 } elseif ( $result === true ) {
02040
02041 return $result;
02042 } else {
02043 return new ResultWrapper( $this, $result );
02044 }
02045 }
02046
02050 function aggregateValue ($valuedata,$valuename='value') {
02051 return $valuename;
02052 }
02053
02062 abstract function getSoftwareLink();
02063
02070 abstract function getServerVersion();
02071
02077 function ping() {
02078 # Stub. Not essential to override.
02079 return true;
02080 }
02081
02086 function getLag() {
02087 if ( !is_null( $this->mFakeSlaveLag ) ) {
02088 wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
02089 return $this->mFakeSlaveLag;
02090 }
02091 $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
02092 # Find slave SQL thread
02093 while ( $row = $this->fetchObject( $res ) ) {
02094
02095
02096
02097
02098
02099
02100 if ( $row->User == 'system user' &&
02101 $row->State != 'Waiting for master to send event' &&
02102 $row->State != 'Connecting to master' &&
02103 $row->State != 'Queueing master event to the relay log' &&
02104 $row->State != 'Waiting for master update' &&
02105 $row->State != 'Requesting binlog dump' &&
02106 $row->State != 'Waiting to reconnect after a failed master event read' &&
02107 $row->State != 'Reconnecting after a failed master event read' &&
02108 $row->State != 'Registering slave on master'
02109 ) {
02110 # This is it, return the time (except -ve)
02111 if ( $row->Time > 0x7fffffff ) {
02112 return false;
02113 } else {
02114 return $row->Time;
02115 }
02116 }
02117 }
02118 return false;
02119 }
02120
02124 function getStatus($which="%") {
02125 $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
02126 $status = array();
02127 while ( $row = $this->fetchObject( $res ) ) {
02128 $status[$row->Variable_name] = $row->Value;
02129 }
02130 return $status;
02131 }
02132
02136 function maxListLen() {
02137 return 0;
02138 }
02139
02140 function encodeBlob($b) {
02141 return $b;
02142 }
02143
02144 function decodeBlob($b) {
02145 return $b;
02146 }
02147
02156 public function setTimeout( $timeout ) {}
02157
02165 function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
02166 $fp = fopen( $filename, 'r' );
02167 if ( false === $fp ) {
02168 if (!defined("MEDIAWIKI_INSTALL"))
02169 throw new MWException( "Could not open \"{$filename}\".\n" );
02170 else
02171 return "Could not open \"{$filename}\".\n";
02172 }
02173 try {
02174 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
02175 }
02176 catch( MWException $e ) {
02177 if ( defined("MEDIAWIKI_INSTALL") ) {
02178 $error = $e->getMessage();
02179 } else {
02180 fclose( $fp );
02181 throw $e;
02182 }
02183 }
02184
02185 fclose( $fp );
02186 return $error;
02187 }
02188
02197 public static function patchPath( $patch ) {
02198 global $wgDBtype, $IP;
02199 if ( file_exists( "$IP/maintenance/$wgDBtype/archives/$patch" ) ) {
02200 return "$IP/maintenance/$wgDBtype/archives/$patch";
02201 } else {
02202 return "$IP/maintenance/archives/$patch";
02203 }
02204 }
02205
02213 function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
02214 $cmd = "";
02215 $done = false;
02216 $dollarquote = false;
02217
02218 while ( ! feof( $fp ) ) {
02219 if ( $lineCallback ) {
02220 call_user_func( $lineCallback );
02221 }
02222 $line = trim( fgets( $fp, 1024 ) );
02223 $sl = strlen( $line ) - 1;
02224
02225 if ( $sl < 0 ) { continue; }
02226 if ( '-' == $line{0} && '-' == $line{1} ) { continue; }
02227
02228 ## Allow dollar quoting for function declarations
02229 if (substr($line,0,4) == '$mw$') {
02230 if ($dollarquote) {
02231 $dollarquote = false;
02232 $done = true;
02233 }
02234 else {
02235 $dollarquote = true;
02236 }
02237 }
02238 else if (!$dollarquote) {
02239 if ( ';' == $line{$sl} && ($sl < 2 || ';' != $line{$sl - 1})) {
02240 $done = true;
02241 $line = substr( $line, 0, $sl );
02242 }
02243 }
02244
02245 if ( $cmd != '' ) { $cmd .= ' '; }
02246 $cmd .= "$line\n";
02247
02248 if ( $done ) {
02249 $cmd = str_replace(';;', ";", $cmd);
02250 $cmd = $this->replaceVars( $cmd );
02251 $res = $this->query( $cmd, __METHOD__ );
02252 if ( $resultCallback ) {
02253 call_user_func( $resultCallback, $res, $this );
02254 }
02255
02256 if ( false === $res ) {
02257 $err = $this->lastError();
02258 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
02259 }
02260
02261 $cmd = '';
02262 $done = false;
02263 }
02264 }
02265 return true;
02266 }
02267
02268
02272 protected function replaceVars( $ins ) {
02273 $varnames = array(
02274 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
02275 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
02276 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
02277 );
02278
02279
02280 foreach ( $varnames as $var ) {
02281 if( isset( $GLOBALS[$var] ) ) {
02282 $val = addslashes( $GLOBALS[$var] );
02283 $ins = str_replace( '{$' . $var . '}', $val, $ins );
02284 $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins );
02285 $ins = str_replace( '/*$' . $var . '*/', $val, $ins );
02286 }
02287 }
02288
02289
02290 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
02291 array( $this, 'tableNameCallback' ), $ins );
02292
02293
02294 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
02295 array( $this, 'indexNameCallback' ), $ins );
02296 return $ins;
02297 }
02298
02303 protected function tableNameCallback( $matches ) {
02304 return $this->tableName( $matches[1] );
02305 }
02306
02310 protected function indexNameCallback( $matches ) {
02311 return $this->indexName( $matches[1] );
02312 }
02313
02319 function buildConcat( $stringList ) {
02320 return 'CONCAT(' . implode( ',', $stringList ) . ')';
02321 }
02322
02333 public function lock( $lockName, $method, $timeout = 5 ) {
02334 return true;
02335 }
02336
02348 public function unlock( $lockName, $method ) {
02349 return true;
02350 }
02351
02360 public function lockTables( $read, $write, $method, $lowPriority = true ) {
02361 return true;
02362 }
02363
02369 public function unlockTables( $method ) {
02370 return true;
02371 }
02372
02379 public function getSearchEngine() {
02380 return "SearchMySQL";
02381 }
02382
02391 public function setBigSelects( $value = true ) {
02392
02393 }
02394 }
02395
02396
02397
02398
02399
02400
02405 class DBObject {
02406 public $mData;
02407
02408 function DBObject($data) {
02409 $this->mData = $data;
02410 }
02411
02412 function isLOB() {
02413 return false;
02414 }
02415
02416 function data() {
02417 return $this->mData;
02418 }
02419 }
02420
02427 class Blob {
02428 private $mData;
02429 function __construct($data) {
02430 $this->mData = $data;
02431 }
02432 function fetch() {
02433 return $this->mData;
02434 }
02435 }
02436
02441 class MySQLField {
02442 private $name, $tablename, $default, $max_length, $nullable,
02443 $is_pk, $is_unique, $is_multiple, $is_key, $type;
02444 function __construct ($info) {
02445 $this->name = $info->name;
02446 $this->tablename = $info->table;
02447 $this->default = $info->def;
02448 $this->max_length = $info->max_length;
02449 $this->nullable = !$info->not_null;
02450 $this->is_pk = $info->primary_key;
02451 $this->is_unique = $info->unique_key;
02452 $this->is_multiple = $info->multiple_key;
02453 $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
02454 $this->type = $info->type;
02455 }
02456
02457 function name() {
02458 return $this->name;
02459 }
02460
02461 function tableName() {
02462 return $this->tableName;
02463 }
02464
02465 function defaultValue() {
02466 return $this->default;
02467 }
02468
02469 function maxLength() {
02470 return $this->max_length;
02471 }
02472
02473 function nullable() {
02474 return $this->nullable;
02475 }
02476
02477 function isKey() {
02478 return $this->is_key;
02479 }
02480
02481 function isMultipleKey() {
02482 return $this->is_multiple;
02483 }
02484
02485 function type() {
02486 return $this->type;
02487 }
02488 }
02489
02490
02491
02492
02493
02498 class DBError extends MWException {
02499 public $db;
02500
02506 function __construct( DatabaseBase &$db, $error ) {
02507 $this->db =& $db;
02508 parent::__construct( $error );
02509 }
02510
02511 function getText() {
02512 global $wgShowDBErrorBacktrace;
02513 $s = $this->getMessage() . "\n";
02514 if ( $wgShowDBErrorBacktrace ) {
02515 $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
02516 }
02517 return $s;
02518 }
02519 }
02520
02524 class DBConnectionError extends DBError {
02525 public $error;
02526
02527 function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
02528 $msg = 'DB connection error';
02529 if ( trim( $error ) != '' ) {
02530 $msg .= ": $error";
02531 }
02532 $this->error = $error;
02533 parent::__construct( $db, $msg );
02534 }
02535
02536 function useOutputPage() {
02537
02538 return false;
02539 }
02540
02541 function useMessageCache() {
02542
02543 return false;
02544 }
02545
02546 function getLogMessage() {
02547 # Don't send to the exception log
02548 return false;
02549 }
02550
02551 function getPageTitle() {
02552 global $wgSitename, $wgLang;
02553 $header = "$wgSitename has a problem";
02554 if ( $wgLang instanceof Language ) {
02555 $header = htmlspecialchars( $wgLang->getMessage( 'dberr-header' ) );
02556 }
02557
02558 return $header;
02559 }
02560
02561 function getHTML() {
02562 global $wgLang, $wgMessageCache, $wgUseFileCache, $wgShowDBErrorBacktrace;
02563
02564 $sorry = 'Sorry! This site is experiencing technical difficulties.';
02565 $again = 'Try waiting a few minutes and reloading.';
02566 $info = '(Can\'t contact the database server: $1)';
02567
02568 if ( $wgLang instanceof Language ) {
02569 $sorry = htmlspecialchars( $wgLang->getMessage( 'dberr-problems' ) );
02570 $again = htmlspecialchars( $wgLang->getMessage( 'dberr-again' ) );
02571 $info = htmlspecialchars( $wgLang->getMessage( 'dberr-info' ) );
02572 }
02573
02574 # No database access
02575 if ( is_object( $wgMessageCache ) ) {
02576 $wgMessageCache->disable();
02577 }
02578
02579 if ( trim( $this->error ) == '' ) {
02580 $this->error = $this->db->getProperty('mServer');
02581 }
02582
02583 $noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
02584 $text = str_replace( '$1', $this->error, $noconnect );
02585
02586 if ( $wgShowDBErrorBacktrace ) {
02587 $text .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
02588 }
02589
02590 $extra = $this->searchForm();
02591
02592 if( $wgUseFileCache ) {
02593 try {
02594 $cache = $this->fileCachedPage();
02595 # Cached version on file system?
02596 if( $cache !== null ) {
02597 # Hack: extend the body for error messages
02598 $cache = str_replace( array('</html>','</body>'), '', $cache );
02599 # Add cache notice...
02600 $cachederror = "This is a cached copy of the requested page, and may not be up to date. ";
02601 # Localize it if possible...
02602 if( $wgLang instanceof Language ) {
02603 $cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
02604 }
02605 $warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>";
02606 # Output cached page with notices on bottom and re-close body
02607 return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>";
02608 }
02609 } catch( MWException $e ) {
02610
02611 }
02612 }
02613 # Headers needed here - output is just the error message
02614 return $this->htmlHeader()."$text<hr />$extra".$this->htmlFooter();
02615 }
02616
02617 function searchForm() {
02618 global $wgSitename, $wgServer, $wgLang, $wgInputEncoding;
02619 $usegoogle = "You can try searching via Google in the meantime.";
02620 $outofdate = "Note that their indexes of our content may be out of date.";
02621 $googlesearch = "Search";
02622
02623 if ( $wgLang instanceof Language ) {
02624 $usegoogle = htmlspecialchars( $wgLang->getMessage( 'dberr-usegoogle' ) );
02625 $outofdate = htmlspecialchars( $wgLang->getMessage( 'dberr-outofdate' ) );
02626 $googlesearch = htmlspecialchars( $wgLang->getMessage( 'searchbutton' ) );
02627 }
02628
02629 $search = htmlspecialchars(@$_REQUEST['search']);
02630
02631 $trygoogle = <<<EOT
02632 <div style="margin: 1.5em">$usegoogle<br />
02633 <small>$outofdate</small></div>
02634 <!-- SiteSearch Google -->
02635 <form method="get" action="http://www.google.com/search" id="googlesearch">
02636 <input type="hidden" name="domains" value="$wgServer" />
02637 <input type="hidden" name="num" value="50" />
02638 <input type="hidden" name="ie" value="$wgInputEncoding" />
02639 <input type="hidden" name="oe" value="$wgInputEncoding" />
02640
02641 <input type="text" name="q" size="31" maxlength="255" value="$search" />
02642 <input type="submit" name="btnG" value="$googlesearch" />
02643 <div>
02644 <input type="radio" name="sitesearch" id="gwiki" value="$wgServer" checked="checked" /><label for="gwiki">$wgSitename</label>
02645 <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
02646 </div>
02647 </form>
02648 <!-- SiteSearch Google -->
02649 EOT;
02650 return $trygoogle;
02651 }
02652
02653 function fileCachedPage() {
02654 global $wgTitle, $title, $wgLang, $wgOut;
02655 if( $wgOut->isDisabled() ) return;
02656 $mainpage = 'Main Page';
02657 if ( $wgLang instanceof Language ) {
02658 $mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
02659 }
02660
02661 if( $wgTitle ) {
02662 $t =& $wgTitle;
02663 } elseif( $title ) {
02664 $t = Title::newFromURL( $title );
02665 } else {
02666 $t = Title::newFromText( $mainpage );
02667 }
02668
02669 $cache = new HTMLFileCache( $t );
02670 if( $cache->isFileCached() ) {
02671 return $cache->fetchPageText();
02672 } else {
02673 return '';
02674 }
02675 }
02676
02677 function htmlBodyOnly() {
02678 return true;
02679 }
02680
02681 }
02682
02686 class DBQueryError extends DBError {
02687 public $error, $errno, $sql, $fname;
02688
02689 function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
02690 $message = "A database error has occurred\n" .
02691 "Query: $sql\n" .
02692 "Function: $fname\n" .
02693 "Error: $errno $error\n";
02694
02695 parent::__construct( $db, $message );
02696 $this->error = $error;
02697 $this->errno = $errno;
02698 $this->sql = $sql;
02699 $this->fname = $fname;
02700 }
02701
02702 function getText() {
02703 global $wgShowDBErrorBacktrace;
02704 if ( $this->useMessageCache() ) {
02705 $s = wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
02706 htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
02707 if ( $wgShowDBErrorBacktrace ) {
02708 $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
02709 }
02710 return $s;
02711 } else {
02712 return parent::getText();
02713 }
02714 }
02715
02716 function getSQL() {
02717 global $wgShowSQLErrors;
02718 if( !$wgShowSQLErrors ) {
02719 return $this->msg( 'sqlhidden', 'SQL hidden' );
02720 } else {
02721 return $this->sql;
02722 }
02723 }
02724
02725 function getLogMessage() {
02726 # Don't send to the exception log
02727 return false;
02728 }
02729
02730 function getPageTitle() {
02731 return $this->msg( 'databaseerror', 'Database error' );
02732 }
02733
02734 function getHTML() {
02735 global $wgShowDBErrorBacktrace;
02736 if ( $this->useMessageCache() ) {
02737 $s = wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
02738 htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
02739 } else {
02740 $s = nl2br( htmlspecialchars( $this->getMessage() ) );
02741 }
02742 if ( $wgShowDBErrorBacktrace ) {
02743 $s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
02744 }
02745 return $s;
02746 }
02747 }
02748
02752 class DBUnexpectedError extends DBError {}
02753
02754
02759 class ResultWrapper implements Iterator {
02760 var $db, $result, $pos = 0, $currentRow = null;
02761
02765 function ResultWrapper( $database, $result ) {
02766 $this->db = $database;
02767 if ( $result instanceof ResultWrapper ) {
02768 $this->result = $result->result;
02769 } else {
02770 $this->result = $result;
02771 }
02772 }
02773
02777 function numRows() {
02778 return $this->db->numRows( $this );
02779 }
02780
02790 function fetchObject() {
02791 return $this->db->fetchObject( $this );
02792 }
02793
02802 function fetchRow() {
02803 return $this->db->fetchRow( $this );
02804 }
02805
02809 function free() {
02810 $this->db->freeResult( $this );
02811 unset( $this->result );
02812 unset( $this->db );
02813 }
02814
02819 function seek( $row ) {
02820 $this->db->dataSeek( $this, $row );
02821 }
02822
02823
02824
02825
02826
02827
02828
02829 function rewind() {
02830 if ($this->numRows()) {
02831 $this->db->dataSeek($this, 0);
02832 }
02833 $this->pos = 0;
02834 $this->currentRow = null;
02835 }
02836
02837 function current() {
02838 if ( is_null( $this->currentRow ) ) {
02839 $this->next();
02840 }
02841 return $this->currentRow;
02842 }
02843
02844 function key() {
02845 return $this->pos;
02846 }
02847
02848 function next() {
02849 $this->pos++;
02850 $this->currentRow = $this->fetchObject();
02851 return $this->currentRow;
02852 }
02853
02854 function valid() {
02855 return $this->current() !== false;
02856 }
02857 }
02858
02863 class LikeMatch {
02864 private $str;
02865
02866 public function __construct( $s ) {
02867 $this->str = $s;
02868 }
02869
02870 public function toString() {
02871 return $this->str;
02872 }
02873 }