MediaWiki
REL1_23
|
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 }