MediaWiki  REL1_24
DatabaseMysqlBase.php
Go to the documentation of this file.
00001 <?php
00032 abstract class DatabaseMysqlBase extends DatabaseBase {
00034     protected $lastKnownSlavePos;
00035 
00037     protected $mFakeSlaveLag = null;
00038 
00039     protected $mFakeMaster = false;
00040 
00044     function getType() {
00045         return 'mysql';
00046     }
00047 
00056     function open( $server, $user, $password, $dbName ) {
00057         global $wgAllDBsAreLocalhost, $wgSQLMode;
00058         wfProfileIn( __METHOD__ );
00059 
00060         # Debugging hack -- fake cluster
00061         if ( $wgAllDBsAreLocalhost ) {
00062             $realServer = 'localhost';
00063         } else {
00064             $realServer = $server;
00065         }
00066         $this->close();
00067         $this->mServer = $server;
00068         $this->mUser = $user;
00069         $this->mPassword = $password;
00070         $this->mDBname = $dbName;
00071 
00072         wfProfileIn( "dbconnect-$server" );
00073 
00074         # The kernel's default SYN retransmission period is far too slow for us,
00075         # so we use a short timeout plus a manual retry. Retrying means that a small
00076         # but finite rate of SYN packet loss won't cause user-visible errors.
00077         $this->mConn = false;
00078         $this->installErrorHandler();
00079         try {
00080             $this->mConn = $this->mysqlConnect( $realServer );
00081         } catch ( Exception $ex ) {
00082             wfProfileOut( "dbconnect-$server" );
00083             wfProfileOut( __METHOD__ );
00084             $this->restoreErrorHandler();
00085             throw $ex;
00086         }
00087         $error = $this->restoreErrorHandler();
00088 
00089         wfProfileOut( "dbconnect-$server" );
00090 
00091         # Always log connection errors
00092         if ( !$this->mConn ) {
00093             if ( !$error ) {
00094                 $error = $this->lastError();
00095             }
00096             wfLogDBError( "Error connecting to {$this->mServer}: $error" );
00097             wfDebug( "DB connection error\n" .
00098                 "Server: $server, User: $user, Password: " .
00099                 substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
00100 
00101             wfProfileOut( __METHOD__ );
00102 
00103             $this->reportConnectionError( $error );
00104         }
00105 
00106         if ( $dbName != '' ) {
00107             wfSuppressWarnings();
00108             $success = $this->selectDB( $dbName );
00109             wfRestoreWarnings();
00110             if ( !$success ) {
00111                 wfLogDBError( "Error selecting database $dbName on server {$this->mServer}" );
00112                 wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
00113                     "from client host " . wfHostname() . "\n" );
00114 
00115                 wfProfileOut( __METHOD__ );
00116 
00117                 $this->reportConnectionError( "Error selecting database $dbName" );
00118             }
00119         }
00120 
00121         // Tell the server what we're communicating with
00122         if ( !$this->connectInitCharset() ) {
00123             $this->reportConnectionError( "Error setting character set" );
00124         }
00125 
00126         // Set SQL mode, default is turning them all off, can be overridden or skipped with null
00127         if ( is_string( $wgSQLMode ) ) {
00128             $mode = $this->addQuotes( $wgSQLMode );
00129             // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
00130             $success = $this->doQuery( "SET sql_mode = $mode", __METHOD__ );
00131             if ( !$success ) {
00132                 wfLogDBError( "Error setting sql_mode to $mode on server {$this->mServer}" );
00133                 wfProfileOut( __METHOD__ );
00134                 $this->reportConnectionError( "Error setting sql_mode to $mode" );
00135             }
00136         }
00137 
00138         $this->mOpened = true;
00139         wfProfileOut( __METHOD__ );
00140 
00141         return true;
00142     }
00143 
00148     protected function connectInitCharset() {
00149         global $wgDBmysql5;
00150 
00151         if ( $wgDBmysql5 ) {
00152             // Tell the server we're communicating with it in UTF-8.
00153             // This may engage various charset conversions.
00154             return $this->mysqlSetCharset( 'utf8' );
00155         } else {
00156             return $this->mysqlSetCharset( 'binary' );
00157         }
00158     }
00159 
00167     abstract protected function mysqlConnect( $realServer );
00168 
00175     abstract protected function mysqlSetCharset( $charset );
00176 
00181     function freeResult( $res ) {
00182         if ( $res instanceof ResultWrapper ) {
00183             $res = $res->result;
00184         }
00185         wfSuppressWarnings();
00186         $ok = $this->mysqlFreeResult( $res );
00187         wfRestoreWarnings();
00188         if ( !$ok ) {
00189             throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
00190         }
00191     }
00192 
00199     abstract protected function mysqlFreeResult( $res );
00200 
00206     function fetchObject( $res ) {
00207         if ( $res instanceof ResultWrapper ) {
00208             $res = $res->result;
00209         }
00210         wfSuppressWarnings();
00211         $row = $this->mysqlFetchObject( $res );
00212         wfRestoreWarnings();
00213 
00214         $errno = $this->lastErrno();
00215         // Unfortunately, mysql_fetch_object does not reset the last errno.
00216         // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
00217         // these are the only errors mysql_fetch_object can cause.
00218         // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
00219         if ( $errno == 2000 || $errno == 2013 ) {
00220             throw new DBUnexpectedError(
00221                 $this,
00222                 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
00223             );
00224         }
00225 
00226         return $row;
00227     }
00228 
00235     abstract protected function mysqlFetchObject( $res );
00236 
00242     function fetchRow( $res ) {
00243         if ( $res instanceof ResultWrapper ) {
00244             $res = $res->result;
00245         }
00246         wfSuppressWarnings();
00247         $row = $this->mysqlFetchArray( $res );
00248         wfRestoreWarnings();
00249 
00250         $errno = $this->lastErrno();
00251         // Unfortunately, mysql_fetch_array does not reset the last errno.
00252         // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
00253         // these are the only errors mysql_fetch_array can cause.
00254         // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
00255         if ( $errno == 2000 || $errno == 2013 ) {
00256             throw new DBUnexpectedError(
00257                 $this,
00258                 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
00259             );
00260         }
00261 
00262         return $row;
00263     }
00264 
00271     abstract protected function mysqlFetchArray( $res );
00272 
00278     function numRows( $res ) {
00279         if ( $res instanceof ResultWrapper ) {
00280             $res = $res->result;
00281         }
00282         wfSuppressWarnings();
00283         $n = $this->mysqlNumRows( $res );
00284         wfRestoreWarnings();
00285 
00286         // Unfortunately, mysql_num_rows does not reset the last errno.
00287         // We are not checking for any errors here, since
00288         // these are no errors mysql_num_rows can cause.
00289         // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
00290         // See https://bugzilla.wikimedia.org/42430
00291         return $n;
00292     }
00293 
00300     abstract protected function mysqlNumRows( $res );
00301 
00306     function numFields( $res ) {
00307         if ( $res instanceof ResultWrapper ) {
00308             $res = $res->result;
00309         }
00310 
00311         return $this->mysqlNumFields( $res );
00312     }
00313 
00320     abstract protected function mysqlNumFields( $res );
00321 
00327     function fieldName( $res, $n ) {
00328         if ( $res instanceof ResultWrapper ) {
00329             $res = $res->result;
00330         }
00331 
00332         return $this->mysqlFieldName( $res, $n );
00333     }
00334 
00342     abstract protected function mysqlFieldName( $res, $n );
00343 
00350     public function fieldType( $res, $n ) {
00351         if ( $res instanceof ResultWrapper ) {
00352             $res = $res->result;
00353         }
00354 
00355         return $this->mysqlFieldType( $res, $n );
00356     }
00357 
00365     abstract protected function mysqlFieldType( $res, $n );
00366 
00372     function dataSeek( $res, $row ) {
00373         if ( $res instanceof ResultWrapper ) {
00374             $res = $res->result;
00375         }
00376 
00377         return $this->mysqlDataSeek( $res, $row );
00378     }
00379 
00387     abstract protected function mysqlDataSeek( $res, $row );
00388 
00392     function lastError() {
00393         if ( $this->mConn ) {
00394             # Even if it's non-zero, it can still be invalid
00395             wfSuppressWarnings();
00396             $error = $this->mysqlError( $this->mConn );
00397             if ( !$error ) {
00398                 $error = $this->mysqlError();
00399             }
00400             wfRestoreWarnings();
00401         } else {
00402             $error = $this->mysqlError();
00403         }
00404         if ( $error ) {
00405             $error .= ' (' . $this->mServer . ')';
00406         }
00407 
00408         return $error;
00409     }
00410 
00417     abstract protected function mysqlError( $conn = null );
00418 
00426     function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
00427         return $this->nativeReplace( $table, $rows, $fname );
00428     }
00429 
00442     public function estimateRowCount( $table, $vars = '*', $conds = '',
00443         $fname = __METHOD__, $options = array()
00444     ) {
00445         $options['EXPLAIN'] = true;
00446         $res = $this->select( $table, $vars, $conds, $fname, $options );
00447         if ( $res === false ) {
00448             return false;
00449         }
00450         if ( !$this->numRows( $res ) ) {
00451             return 0;
00452         }
00453 
00454         $rows = 1;
00455         foreach ( $res as $plan ) {
00456             $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
00457         }
00458 
00459         return $rows;
00460     }
00461 
00467     function fieldInfo( $table, $field ) {
00468         $table = $this->tableName( $table );
00469         $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
00470         if ( !$res ) {
00471             return false;
00472         }
00473         $n = $this->mysqlNumFields( $res->result );
00474         for ( $i = 0; $i < $n; $i++ ) {
00475             $meta = $this->mysqlFetchField( $res->result, $i );
00476             if ( $field == $meta->name ) {
00477                 return new MySQLField( $meta );
00478             }
00479         }
00480 
00481         return false;
00482     }
00483 
00491     abstract protected function mysqlFetchField( $res, $n );
00492 
00502     function indexInfo( $table, $index, $fname = __METHOD__ ) {
00503         # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
00504         # SHOW INDEX should work for 3.x and up:
00505         # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
00506         $table = $this->tableName( $table );
00507         $index = $this->indexName( $index );
00508 
00509         $sql = 'SHOW INDEX FROM ' . $table;
00510         $res = $this->query( $sql, $fname );
00511 
00512         if ( !$res ) {
00513             return null;
00514         }
00515 
00516         $result = array();
00517 
00518         foreach ( $res as $row ) {
00519             if ( $row->Key_name == $index ) {
00520                 $result[] = $row;
00521             }
00522         }
00523 
00524         return empty( $result ) ? false : $result;
00525     }
00526 
00531     function strencode( $s ) {
00532         $sQuoted = $this->mysqlRealEscapeString( $s );
00533 
00534         if ( $sQuoted === false ) {
00535             $this->ping();
00536             $sQuoted = $this->mysqlRealEscapeString( $s );
00537         }
00538 
00539         return $sQuoted;
00540     }
00541 
00548     public function addIdentifierQuotes( $s ) {
00549         // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
00550         // Remove NUL bytes and escape backticks by doubling
00551         return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
00552     }
00553 
00558     public function isQuotedIdentifier( $name ) {
00559         return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
00560     }
00561 
00565     function ping() {
00566         $ping = $this->mysqlPing();
00567         if ( $ping ) {
00568             return true;
00569         }
00570 
00571         $this->closeConnection();
00572         $this->mOpened = false;
00573         $this->mConn = false;
00574         $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
00575 
00576         return true;
00577     }
00578 
00584     abstract protected function mysqlPing();
00585 
00591     public function setFakeSlaveLag( $lag ) {
00592         $this->mFakeSlaveLag = $lag;
00593     }
00594 
00600     public function setFakeMaster( $enabled = true ) {
00601         $this->mFakeMaster = $enabled;
00602     }
00603 
00611     function getLag() {
00612         if ( !is_null( $this->mFakeSlaveLag ) ) {
00613             wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
00614 
00615             return $this->mFakeSlaveLag;
00616         }
00617 
00618         return $this->getLagFromSlaveStatus();
00619     }
00620 
00624     function getLagFromSlaveStatus() {
00625         $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
00626         if ( !$res ) {
00627             return false;
00628         }
00629         $row = $res->fetchObject();
00630         if ( !$row ) {
00631             return false;
00632         }
00633         if ( strval( $row->Seconds_Behind_Master ) === '' ) {
00634             return false;
00635         } else {
00636             return intval( $row->Seconds_Behind_Master );
00637         }
00638     }
00639 
00650     function masterPosWait( DBMasterPos $pos, $timeout ) {
00651         if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
00652             return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
00653         }
00654 
00655         wfProfileIn( __METHOD__ );
00656         # Commit any open transactions
00657         $this->commit( __METHOD__, 'flush' );
00658 
00659         if ( !is_null( $this->mFakeSlaveLag ) ) {
00660             $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
00661 
00662             if ( $wait > $timeout * 1e6 ) {
00663                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
00664                 wfProfileOut( __METHOD__ );
00665 
00666                 return -1;
00667             } elseif ( $wait > 0 ) {
00668                 wfDebug( "Fake slave waiting $wait us\n" );
00669                 usleep( $wait );
00670                 wfProfileOut( __METHOD__ );
00671 
00672                 return 1;
00673             } else {
00674                 wfDebug( "Fake slave up to date ($wait us)\n" );
00675                 wfProfileOut( __METHOD__ );
00676 
00677                 return 0;
00678             }
00679         }
00680 
00681         # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
00682         $encFile = $this->addQuotes( $pos->file );
00683         $encPos = intval( $pos->pos );
00684         $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
00685         $res = $this->doQuery( $sql );
00686 
00687         $status = false;
00688         if ( $res && $row = $this->fetchRow( $res ) ) {
00689             $status = $row[0]; // can be NULL, -1, or 0+ per the MySQL manual
00690             if ( ctype_digit( $status ) ) { // success
00691                 $this->lastKnownSlavePos = $pos;
00692             }
00693         }
00694 
00695         wfProfileOut( __METHOD__ );
00696 
00697         return $status;
00698     }
00699 
00705     function getSlavePos() {
00706         if ( !is_null( $this->mFakeSlaveLag ) ) {
00707             $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
00708             wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
00709 
00710             return $pos;
00711         }
00712 
00713         $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
00714         $row = $this->fetchObject( $res );
00715 
00716         if ( $row ) {
00717             $pos = isset( $row->Exec_master_log_pos )
00718                 ? $row->Exec_master_log_pos
00719                 : $row->Exec_Master_Log_Pos;
00720 
00721             return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
00722         } else {
00723             return false;
00724         }
00725     }
00726 
00732     function getMasterPos() {
00733         if ( $this->mFakeMaster ) {
00734             return new MySQLMasterPos( 'fake', microtime( true ) );
00735         }
00736 
00737         $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
00738         $row = $this->fetchObject( $res );
00739 
00740         if ( $row ) {
00741             return new MySQLMasterPos( $row->File, $row->Position );
00742         } else {
00743             return false;
00744         }
00745     }
00746 
00751     function useIndexClause( $index ) {
00752         return "FORCE INDEX (" . $this->indexName( $index ) . ")";
00753     }
00754 
00758     function lowPriorityOption() {
00759         return 'LOW_PRIORITY';
00760     }
00761 
00765     public function getSoftwareLink() {
00766         // MariaDB includes its name in its version string (sent when the connection is opened),
00767         // and this is how MariaDB's version of the mysql command-line client identifies MariaDB
00768         // servers (see the mariadb_connection() function in libmysql/libmysql.c).
00769         $version = $this->getServerVersion();
00770         if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
00771             return '[{{int:version-db-mariadb-url}} MariaDB]';
00772         }
00773 
00774         // Percona Server's version suffix is not very distinctive, and @@version_comment
00775         // doesn't give the necessary info for source builds, so assume the server is MySQL.
00776         // (Even Percona's version of mysql doesn't try to make the distinction.)
00777         return '[{{int:version-db-mysql-url}} MySQL]';
00778     }
00779 
00783     public function setSessionOptions( array $options ) {
00784         if ( isset( $options['connTimeout'] ) ) {
00785             $timeout = (int)$options['connTimeout'];
00786             $this->query( "SET net_read_timeout=$timeout" );
00787             $this->query( "SET net_write_timeout=$timeout" );
00788         }
00789     }
00790 
00796     public function streamStatementEnd( &$sql, &$newLine ) {
00797         if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
00798             preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
00799             $this->delimiter = $m[1];
00800             $newLine = '';
00801         }
00802 
00803         return parent::streamStatementEnd( $sql, $newLine );
00804     }
00805 
00814     public function lockIsFree( $lockName, $method ) {
00815         $lockName = $this->addQuotes( $lockName );
00816         $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
00817         $row = $this->fetchObject( $result );
00818 
00819         return ( $row->lockstatus == 1 );
00820     }
00821 
00828     public function lock( $lockName, $method, $timeout = 5 ) {
00829         $lockName = $this->addQuotes( $lockName );
00830         $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
00831         $row = $this->fetchObject( $result );
00832 
00833         if ( $row->lockstatus == 1 ) {
00834             return true;
00835         } else {
00836             wfDebug( __METHOD__ . " failed to acquire lock\n" );
00837 
00838             return false;
00839         }
00840     }
00841 
00849     public function unlock( $lockName, $method ) {
00850         $lockName = $this->addQuotes( $lockName );
00851         $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
00852         $row = $this->fetchObject( $result );
00853 
00854         return ( $row->lockstatus == 1 );
00855     }
00856 
00864     public function lockTables( $read, $write, $method, $lowPriority = true ) {
00865         $items = array();
00866 
00867         foreach ( $write as $table ) {
00868             $tbl = $this->tableName( $table ) .
00869                 ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
00870                 ' WRITE';
00871             $items[] = $tbl;
00872         }
00873         foreach ( $read as $table ) {
00874             $items[] = $this->tableName( $table ) . ' READ';
00875         }
00876         $sql = "LOCK TABLES " . implode( ',', $items );
00877         $this->query( $sql, $method );
00878 
00879         return true;
00880     }
00881 
00886     public function unlockTables( $method ) {
00887         $this->query( "UNLOCK TABLES", $method );
00888 
00889         return true;
00890     }
00891 
00898     public function getSearchEngine() {
00899         return 'SearchMySQL';
00900     }
00901 
00905     public function setBigSelects( $value = true ) {
00906         if ( $value === 'default' ) {
00907             if ( $this->mDefaultBigSelects === null ) {
00908                 # Function hasn't been called before so it must already be set to the default
00909                 return;
00910             } else {
00911                 $value = $this->mDefaultBigSelects;
00912             }
00913         } elseif ( $this->mDefaultBigSelects === null ) {
00914             $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
00915         }
00916         $encValue = $value ? '1' : '0';
00917         $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
00918     }
00919 
00931     function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
00932         if ( !$conds ) {
00933             throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
00934         }
00935 
00936         $delTable = $this->tableName( $delTable );
00937         $joinTable = $this->tableName( $joinTable );
00938         $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
00939 
00940         if ( $conds != '*' ) {
00941             $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
00942         }
00943 
00944         return $this->query( $sql, $fname );
00945     }
00946 
00955     public function upsert( $table, array $rows, array $uniqueIndexes,
00956         array $set, $fname = __METHOD__
00957     ) {
00958         if ( !count( $rows ) ) {
00959             return true; // nothing to do
00960         }
00961 
00962         if ( !is_array( reset( $rows ) ) ) {
00963             $rows = array( $rows );
00964         }
00965 
00966         $table = $this->tableName( $table );
00967         $columns = array_keys( $rows[0] );
00968 
00969         $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
00970         $rowTuples = array();
00971         foreach ( $rows as $row ) {
00972             $rowTuples[] = '(' . $this->makeList( $row ) . ')';
00973         }
00974         $sql .= implode( ',', $rowTuples );
00975         $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, LIST_SET );
00976 
00977         return (bool)$this->query( $sql, $fname );
00978     }
00979 
00985     function getServerUptime() {
00986         $vars = $this->getMysqlStatus( 'Uptime' );
00987 
00988         return (int)$vars['Uptime'];
00989     }
00990 
00996     function wasDeadlock() {
00997         return $this->lastErrno() == 1213;
00998     }
00999 
01005     function wasLockTimeout() {
01006         return $this->lastErrno() == 1205;
01007     }
01008 
01015     function wasErrorReissuable() {
01016         return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
01017     }
01018 
01024     function wasReadOnlyError() {
01025         return $this->lastErrno() == 1223 ||
01026             ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
01027     }
01028 
01036     function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
01037         $tmp = $temporary ? 'TEMPORARY ' : '';
01038         $newName = $this->addIdentifierQuotes( $newName );
01039         $oldName = $this->addIdentifierQuotes( $oldName );
01040         $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
01041 
01042         return $this->query( $query, $fname );
01043     }
01044 
01052     function listTables( $prefix = null, $fname = __METHOD__ ) {
01053         $result = $this->query( "SHOW TABLES", $fname );
01054 
01055         $endArray = array();
01056 
01057         foreach ( $result as $table ) {
01058             $vars = get_object_vars( $table );
01059             $table = array_pop( $vars );
01060 
01061             if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
01062                 $endArray[] = $table;
01063             }
01064         }
01065 
01066         return $endArray;
01067     }
01068 
01074     public function dropTable( $tableName, $fName = __METHOD__ ) {
01075         if ( !$this->tableExists( $tableName, $fName ) ) {
01076             return false;
01077         }
01078 
01079         return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
01080     }
01081 
01085     protected function getDefaultSchemaVars() {
01086         $vars = parent::getDefaultSchemaVars();
01087         $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
01088         $vars['wgDBTableOptions'] = str_replace(
01089             'CHARSET=mysql4',
01090             'CHARSET=binary',
01091             $vars['wgDBTableOptions']
01092         );
01093 
01094         return $vars;
01095     }
01096 
01103     function getMysqlStatus( $which = "%" ) {
01104         $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
01105         $status = array();
01106 
01107         foreach ( $res as $row ) {
01108             $status[$row->Variable_name] = $row->Value;
01109         }
01110 
01111         return $status;
01112     }
01113 
01123     public function listViews( $prefix = null, $fname = __METHOD__ ) {
01124 
01125         if ( !isset( $this->allViews ) ) {
01126 
01127             // The name of the column containing the name of the VIEW
01128             $propertyName = 'Tables_in_' . $this->mDBname;
01129 
01130             // Query for the VIEWS
01131             $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
01132             $this->allViews = array();
01133             while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
01134                 array_push( $this->allViews, $row[$propertyName] );
01135             }
01136         }
01137 
01138         if ( is_null( $prefix ) || $prefix === '' ) {
01139             return $this->allViews;
01140         }
01141 
01142         $filteredViews = array();
01143         foreach ( $this->allViews as $viewName ) {
01144             // Does the name of this VIEW start with the table-prefix?
01145             if ( strpos( $viewName, $prefix ) === 0 ) {
01146                 array_push( $filteredViews, $viewName );
01147             }
01148         }
01149 
01150         return $filteredViews;
01151     }
01152 
01161     public function isView( $name, $prefix = null ) {
01162         return in_array( $name, $this->listViews( $prefix ) );
01163     }
01164 }
01165 
01170 class MySQLField implements Field {
01171     private $name, $tablename, $default, $max_length, $nullable,
01172         $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
01173 
01174     function __construct( $info ) {
01175         $this->name = $info->name;
01176         $this->tablename = $info->table;
01177         $this->default = $info->def;
01178         $this->max_length = $info->max_length;
01179         $this->nullable = !$info->not_null;
01180         $this->is_pk = $info->primary_key;
01181         $this->is_unique = $info->unique_key;
01182         $this->is_multiple = $info->multiple_key;
01183         $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
01184         $this->type = $info->type;
01185         $this->binary = isset( $info->binary ) ? $info->binary : false;
01186     }
01187 
01191     function name() {
01192         return $this->name;
01193     }
01194 
01198     function tableName() {
01199         return $this->tableName;
01200     }
01201 
01205     function type() {
01206         return $this->type;
01207     }
01208 
01212     function isNullable() {
01213         return $this->nullable;
01214     }
01215 
01216     function defaultValue() {
01217         return $this->default;
01218     }
01219 
01223     function isKey() {
01224         return $this->is_key;
01225     }
01226 
01230     function isMultipleKey() {
01231         return $this->is_multiple;
01232     }
01233 
01234     function isBinary() {
01235         return $this->binary;
01236     }
01237 }
01238 
01239 class MySQLMasterPos implements DBMasterPos {
01241     public $file;
01242 
01244     public $pos;
01245 
01246     function __construct( $file, $pos ) {
01247         $this->file = $file;
01248         $this->pos = $pos;
01249     }
01250 
01251     function __toString() {
01252         // e.g db1034-bin.000976/843431247
01253         return "{$this->file}/{$this->pos}";
01254     }
01255 
01259     protected function getCoordinates() {
01260         $m = array();
01261         if ( preg_match( '!\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
01262             return array( (int)$m[1], (int)$m[2] );
01263         }
01264 
01265         return false;
01266     }
01267 
01268     function hasReached( MySQLMasterPos $pos ) {
01269         $thisPos = $this->getCoordinates();
01270         $thatPos = $pos->getCoordinates();
01271 
01272         return ( $thisPos && $thatPos && $thisPos >= $thatPos );
01273     }
01274 }