MediaWiki  REL1_23
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 
00645     function getLagFromProcesslist() {
00646         wfDeprecated( __METHOD__, '1.19' );
00647         $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
00648         if ( !$res ) {
00649             return false;
00650         }
00651         # Find slave SQL thread
00652         foreach ( $res as $row ) {
00653             /* This should work for most situations - when default db
00654              * for thread is not specified, it had no events executed,
00655              * and therefore it doesn't know yet how lagged it is.
00656              *
00657              * Relay log I/O thread does not select databases.
00658              */
00659             if ( $row->User == 'system user' &&
00660                 $row->State != 'Waiting for master to send event' &&
00661                 $row->State != 'Connecting to master' &&
00662                 $row->State != 'Queueing master event to the relay log' &&
00663                 $row->State != 'Waiting for master update' &&
00664                 $row->State != 'Requesting binlog dump' &&
00665                 $row->State != 'Waiting to reconnect after a failed master event read' &&
00666                 $row->State != 'Reconnecting after a failed master event read' &&
00667                 $row->State != 'Registering slave on master'
00668             ) {
00669                 # This is it, return the time (except -ve)
00670                 if ( $row->Time > 0x7fffffff ) {
00671                     return false;
00672                 } else {
00673                     return $row->Time;
00674                 }
00675             }
00676         }
00677 
00678         return false;
00679     }
00680 
00691     function masterPosWait( DBMasterPos $pos, $timeout ) {
00692         if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
00693             return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
00694         }
00695 
00696         wfProfileIn( __METHOD__ );
00697         # Commit any open transactions
00698         $this->commit( __METHOD__, 'flush' );
00699 
00700         if ( !is_null( $this->mFakeSlaveLag ) ) {
00701             $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
00702 
00703             if ( $wait > $timeout * 1e6 ) {
00704                 wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
00705                 wfProfileOut( __METHOD__ );
00706 
00707                 return -1;
00708             } elseif ( $wait > 0 ) {
00709                 wfDebug( "Fake slave waiting $wait us\n" );
00710                 usleep( $wait );
00711                 wfProfileOut( __METHOD__ );
00712 
00713                 return 1;
00714             } else {
00715                 wfDebug( "Fake slave up to date ($wait us)\n" );
00716                 wfProfileOut( __METHOD__ );
00717 
00718                 return 0;
00719             }
00720         }
00721 
00722         # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
00723         $encFile = $this->addQuotes( $pos->file );
00724         $encPos = intval( $pos->pos );
00725         $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
00726         $res = $this->doQuery( $sql );
00727 
00728         $status = false;
00729         if ( $res && $row = $this->fetchRow( $res ) ) {
00730             $status = $row[0]; // can be NULL, -1, or 0+ per the MySQL manual
00731             if ( ctype_digit( $status ) ) { // success
00732                 $this->lastKnownSlavePos = $pos;
00733             }
00734         }
00735 
00736         wfProfileOut( __METHOD__ );
00737 
00738         return $status;
00739     }
00740 
00746     function getSlavePos() {
00747         if ( !is_null( $this->mFakeSlaveLag ) ) {
00748             $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
00749             wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
00750 
00751             return $pos;
00752         }
00753 
00754         $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
00755         $row = $this->fetchObject( $res );
00756 
00757         if ( $row ) {
00758             $pos = isset( $row->Exec_master_log_pos )
00759                 ? $row->Exec_master_log_pos
00760                 : $row->Exec_Master_Log_Pos;
00761 
00762             return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
00763         } else {
00764             return false;
00765         }
00766     }
00767 
00773     function getMasterPos() {
00774         if ( $this->mFakeMaster ) {
00775             return new MySQLMasterPos( 'fake', microtime( true ) );
00776         }
00777 
00778         $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
00779         $row = $this->fetchObject( $res );
00780 
00781         if ( $row ) {
00782             return new MySQLMasterPos( $row->File, $row->Position );
00783         } else {
00784             return false;
00785         }
00786     }
00787 
00792     function useIndexClause( $index ) {
00793         return "FORCE INDEX (" . $this->indexName( $index ) . ")";
00794     }
00795 
00799     function lowPriorityOption() {
00800         return 'LOW_PRIORITY';
00801     }
00802 
00806     public function getSoftwareLink() {
00807         // MariaDB includes its name in its version string (sent when the connection is opened),
00808         // and this is how MariaDB's version of the mysql command-line client identifies MariaDB
00809         // servers (see the mariadb_connection() function in libmysql/libmysql.c).
00810         $version = $this->getServerVersion();
00811         if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
00812             return '[{{int:version-db-mariadb-url}} MariaDB]';
00813         }
00814 
00815         // Percona Server's version suffix is not very distinctive, and @@version_comment
00816         // doesn't give the necessary info for source builds, so assume the server is MySQL.
00817         // (Even Percona's version of mysql doesn't try to make the distinction.)
00818         return '[{{int:version-db-mysql-url}} MySQL]';
00819     }
00820 
00824     public function setSessionOptions( array $options ) {
00825         if ( isset( $options['connTimeout'] ) ) {
00826             $timeout = (int)$options['connTimeout'];
00827             $this->query( "SET net_read_timeout=$timeout" );
00828             $this->query( "SET net_write_timeout=$timeout" );
00829         }
00830     }
00831 
00837     public function streamStatementEnd( &$sql, &$newLine ) {
00838         if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
00839             preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
00840             $this->delimiter = $m[1];
00841             $newLine = '';
00842         }
00843 
00844         return parent::streamStatementEnd( $sql, $newLine );
00845     }
00846 
00855     public function lockIsFree( $lockName, $method ) {
00856         $lockName = $this->addQuotes( $lockName );
00857         $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
00858         $row = $this->fetchObject( $result );
00859 
00860         return ( $row->lockstatus == 1 );
00861     }
00862 
00869     public function lock( $lockName, $method, $timeout = 5 ) {
00870         $lockName = $this->addQuotes( $lockName );
00871         $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
00872         $row = $this->fetchObject( $result );
00873 
00874         if ( $row->lockstatus == 1 ) {
00875             return true;
00876         } else {
00877             wfDebug( __METHOD__ . " failed to acquire lock\n" );
00878 
00879             return false;
00880         }
00881     }
00882 
00890     public function unlock( $lockName, $method ) {
00891         $lockName = $this->addQuotes( $lockName );
00892         $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
00893         $row = $this->fetchObject( $result );
00894 
00895         return ( $row->lockstatus == 1 );
00896     }
00897 
00905     public function lockTables( $read, $write, $method, $lowPriority = true ) {
00906         $items = array();
00907 
00908         foreach ( $write as $table ) {
00909             $tbl = $this->tableName( $table ) .
00910                 ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
00911                 ' WRITE';
00912             $items[] = $tbl;
00913         }
00914         foreach ( $read as $table ) {
00915             $items[] = $this->tableName( $table ) . ' READ';
00916         }
00917         $sql = "LOCK TABLES " . implode( ',', $items );
00918         $this->query( $sql, $method );
00919 
00920         return true;
00921     }
00922 
00927     public function unlockTables( $method ) {
00928         $this->query( "UNLOCK TABLES", $method );
00929 
00930         return true;
00931     }
00932 
00939     public function getSearchEngine() {
00940         return 'SearchMySQL';
00941     }
00942 
00947     public function setBigSelects( $value = true ) {
00948         if ( $value === 'default' ) {
00949             if ( $this->mDefaultBigSelects === null ) {
00950                 # Function hasn't been called before so it must already be set to the default
00951                 return;
00952             } else {
00953                 $value = $this->mDefaultBigSelects;
00954             }
00955         } elseif ( $this->mDefaultBigSelects === null ) {
00956             $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
00957         }
00958         $encValue = $value ? '1' : '0';
00959         $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
00960     }
00961 
00973     function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
00974         if ( !$conds ) {
00975             throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
00976         }
00977 
00978         $delTable = $this->tableName( $delTable );
00979         $joinTable = $this->tableName( $joinTable );
00980         $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
00981 
00982         if ( $conds != '*' ) {
00983             $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
00984         }
00985 
00986         return $this->query( $sql, $fname );
00987     }
00988 
00997     public function upsert( $table, array $rows, array $uniqueIndexes,
00998         array $set, $fname = __METHOD__
00999     ) {
01000         if ( !count( $rows ) ) {
01001             return true; // nothing to do
01002         }
01003 
01004         if ( !is_array( reset( $rows ) ) ) {
01005             $rows = array( $rows );
01006         }
01007 
01008         $table = $this->tableName( $table );
01009         $columns = array_keys( $rows[0] );
01010 
01011         $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
01012         $rowTuples = array();
01013         foreach ( $rows as $row ) {
01014             $rowTuples[] = '(' . $this->makeList( $row ) . ')';
01015         }
01016         $sql .= implode( ',', $rowTuples );
01017         $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, LIST_SET );
01018 
01019         return (bool)$this->query( $sql, $fname );
01020     }
01021 
01027     function getServerUptime() {
01028         $vars = $this->getMysqlStatus( 'Uptime' );
01029 
01030         return (int)$vars['Uptime'];
01031     }
01032 
01038     function wasDeadlock() {
01039         return $this->lastErrno() == 1213;
01040     }
01041 
01047     function wasLockTimeout() {
01048         return $this->lastErrno() == 1205;
01049     }
01050 
01057     function wasErrorReissuable() {
01058         return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
01059     }
01060 
01066     function wasReadOnlyError() {
01067         return $this->lastErrno() == 1223 ||
01068             ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
01069     }
01070 
01078     function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
01079         $tmp = $temporary ? 'TEMPORARY ' : '';
01080         $newName = $this->addIdentifierQuotes( $newName );
01081         $oldName = $this->addIdentifierQuotes( $oldName );
01082         $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
01083 
01084         return $this->query( $query, $fname );
01085     }
01086 
01094     function listTables( $prefix = null, $fname = __METHOD__ ) {
01095         $result = $this->query( "SHOW TABLES", $fname );
01096 
01097         $endArray = array();
01098 
01099         foreach ( $result as $table ) {
01100             $vars = get_object_vars( $table );
01101             $table = array_pop( $vars );
01102 
01103             if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
01104                 $endArray[] = $table;
01105             }
01106         }
01107 
01108         return $endArray;
01109     }
01110 
01116     public function dropTable( $tableName, $fName = __METHOD__ ) {
01117         if ( !$this->tableExists( $tableName, $fName ) ) {
01118             return false;
01119         }
01120 
01121         return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
01122     }
01123 
01127     protected function getDefaultSchemaVars() {
01128         $vars = parent::getDefaultSchemaVars();
01129         $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
01130         $vars['wgDBTableOptions'] = str_replace(
01131             'CHARSET=mysql4',
01132             'CHARSET=binary',
01133             $vars['wgDBTableOptions']
01134         );
01135 
01136         return $vars;
01137     }
01138 
01145     function getMysqlStatus( $which = "%" ) {
01146         $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
01147         $status = array();
01148 
01149         foreach ( $res as $row ) {
01150             $status[$row->Variable_name] = $row->Value;
01151         }
01152 
01153         return $status;
01154     }
01155 
01165     public function listViews( $prefix = null, $fname = __METHOD__ ) {
01166 
01167         if ( !isset( $this->allViews ) ) {
01168 
01169             // The name of the column containing the name of the VIEW
01170             $propertyName = 'Tables_in_' . $this->mDBname;
01171 
01172             // Query for the VIEWS
01173             $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
01174             $this->allViews = array();
01175             while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
01176                 array_push( $this->allViews, $row[$propertyName] );
01177             }
01178         }
01179 
01180         if ( is_null( $prefix ) || $prefix === '' ) {
01181             return $this->allViews;
01182         }
01183 
01184         $filteredViews = array();
01185         foreach ( $this->allViews as $viewName ) {
01186             // Does the name of this VIEW start with the table-prefix?
01187             if ( strpos( $viewName, $prefix ) === 0 ) {
01188                 array_push( $filteredViews, $viewName );
01189             }
01190         }
01191 
01192         return $filteredViews;
01193     }
01194 
01203     public function isView( $name, $prefix = null ) {
01204         return in_array( $name, $this->listViews( $prefix ) );
01205     }
01206 }
01207 
01212 class MySQLField implements Field {
01213     private $name, $tablename, $default, $max_length, $nullable,
01214         $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
01215 
01216     function __construct( $info ) {
01217         $this->name = $info->name;
01218         $this->tablename = $info->table;
01219         $this->default = $info->def;
01220         $this->max_length = $info->max_length;
01221         $this->nullable = !$info->not_null;
01222         $this->is_pk = $info->primary_key;
01223         $this->is_unique = $info->unique_key;
01224         $this->is_multiple = $info->multiple_key;
01225         $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
01226         $this->type = $info->type;
01227         $this->binary = isset( $info->binary ) ? $info->binary : false;
01228     }
01229 
01233     function name() {
01234         return $this->name;
01235     }
01236 
01240     function tableName() {
01241         return $this->tableName;
01242     }
01243 
01247     function type() {
01248         return $this->type;
01249     }
01250 
01254     function isNullable() {
01255         return $this->nullable;
01256     }
01257 
01258     function defaultValue() {
01259         return $this->default;
01260     }
01261 
01265     function isKey() {
01266         return $this->is_key;
01267     }
01268 
01272     function isMultipleKey() {
01273         return $this->is_multiple;
01274     }
01275 
01276     function isBinary() {
01277         return $this->binary;
01278     }
01279 }
01280 
01281 class MySQLMasterPos implements DBMasterPos {
01283     public $file;
01284 
01286     public $pos;
01287 
01288     function __construct( $file, $pos ) {
01289         $this->file = $file;
01290         $this->pos = $pos;
01291     }
01292 
01293     function __toString() {
01294         // e.g db1034-bin.000976/843431247
01295         return "{$this->file}/{$this->pos}";
01296     }
01297 
01301     protected function getCoordinates() {
01302         $m = array();
01303         if ( preg_match( '!\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
01304             return array( (int)$m[1], (int)$m[2] );
01305         }
01306 
01307         return false;
01308     }
01309 
01310     function hasReached( MySQLMasterPos $pos ) {
01311         $thisPos = $this->getCoordinates();
01312         $thatPos = $pos->getCoordinates();
01313 
01314         return ( $thisPos && $thatPos && $thisPos >= $thatPos );
01315     }
01316 }