MediaWiki
REL1_19
|
00001 <?php 00016 class DatabaseMysql extends DatabaseBase { 00017 00021 function getType() { 00022 return 'mysql'; 00023 } 00024 00029 protected function doQuery( $sql ) { 00030 if( $this->bufferResults() ) { 00031 $ret = mysql_query( $sql, $this->mConn ); 00032 } else { 00033 $ret = mysql_unbuffered_query( $sql, $this->mConn ); 00034 } 00035 return $ret; 00036 } 00037 00046 function open( $server, $user, $password, $dbName ) { 00047 global $wgAllDBsAreLocalhost; 00048 wfProfileIn( __METHOD__ ); 00049 00050 # Load mysql.so if we don't have it 00051 wfDl( 'mysql' ); 00052 00053 # Fail now 00054 # Otherwise we get a suppressed fatal error, which is very hard to track down 00055 if ( !function_exists( 'mysql_connect' ) ) { 00056 throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" ); 00057 } 00058 00059 # Debugging hack -- fake cluster 00060 if ( $wgAllDBsAreLocalhost ) { 00061 $realServer = 'localhost'; 00062 } else { 00063 $realServer = $server; 00064 } 00065 $this->close(); 00066 $this->mServer = $server; 00067 $this->mUser = $user; 00068 $this->mPassword = $password; 00069 $this->mDBname = $dbName; 00070 00071 wfProfileIn("dbconnect-$server"); 00072 00073 # The kernel's default SYN retransmission period is far too slow for us, 00074 # so we use a short timeout plus a manual retry. Retrying means that a small 00075 # but finite rate of SYN packet loss won't cause user-visible errors. 00076 $this->mConn = false; 00077 if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) { 00078 $numAttempts = 2; 00079 } else { 00080 $numAttempts = 1; 00081 } 00082 $this->installErrorHandler(); 00083 for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) { 00084 if ( $i > 1 ) { 00085 usleep( 1000 ); 00086 } 00087 if ( $this->mFlags & DBO_PERSISTENT ) { 00088 $this->mConn = mysql_pconnect( $realServer, $user, $password ); 00089 } else { 00090 # Create a new connection... 00091 $this->mConn = mysql_connect( $realServer, $user, $password, true ); 00092 } 00093 #if ( $this->mConn === false ) { 00094 #$iplus = $i + 1; 00095 #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); 00096 #} 00097 } 00098 $phpError = $this->restoreErrorHandler(); 00099 # Always log connection errors 00100 if ( !$this->mConn ) { 00101 $error = $this->lastError(); 00102 if ( !$error ) { 00103 $error = $phpError; 00104 } 00105 wfLogDBError( "Error connecting to {$this->mServer}: $error\n" ); 00106 wfDebug( "DB connection error\n" ); 00107 wfDebug( "Server: $server, User: $user, Password: " . 00108 substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" ); 00109 } 00110 00111 wfProfileOut("dbconnect-$server"); 00112 00113 if ( $dbName != '' && $this->mConn !== false ) { 00114 wfSuppressWarnings(); 00115 $success = mysql_select_db( $dbName, $this->mConn ); 00116 wfRestoreWarnings(); 00117 if ( !$success ) { 00118 $error = "Error selecting database $dbName on server {$this->mServer} " . 00119 "from client host " . wfHostname() . "\n"; 00120 wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n"); 00121 wfDebug( $error ); 00122 } 00123 } else { 00124 # Delay USE query 00125 $success = (bool)$this->mConn; 00126 } 00127 00128 if ( $success ) { 00129 // Tell the server we're communicating with it in UTF-8. 00130 // This may engage various charset conversions. 00131 global $wgDBmysql5; 00132 if( $wgDBmysql5 ) { 00133 $this->query( 'SET NAMES utf8', __METHOD__ ); 00134 } else { 00135 $this->query( 'SET NAMES binary', __METHOD__ ); 00136 } 00137 // Set SQL mode, default is turning them all off, can be overridden or skipped with null 00138 global $wgSQLMode; 00139 if ( is_string( $wgSQLMode ) ) { 00140 $mode = $this->addQuotes( $wgSQLMode ); 00141 $this->query( "SET sql_mode = $mode", __METHOD__ ); 00142 } 00143 00144 // Turn off strict mode if it is on 00145 } else { 00146 $this->reportConnectionError( $phpError ); 00147 } 00148 00149 $this->mOpened = $success; 00150 wfProfileOut( __METHOD__ ); 00151 return $success; 00152 } 00153 00157 function close() { 00158 $this->mOpened = false; 00159 if ( $this->mConn ) { 00160 if ( $this->trxLevel() ) { 00161 $this->commit(); 00162 } 00163 return mysql_close( $this->mConn ); 00164 } else { 00165 return true; 00166 } 00167 } 00168 00173 function freeResult( $res ) { 00174 if ( $res instanceof ResultWrapper ) { 00175 $res = $res->result; 00176 } 00177 wfSuppressWarnings(); 00178 $ok = mysql_free_result( $res ); 00179 wfRestoreWarnings(); 00180 if ( !$ok ) { 00181 throw new DBUnexpectedError( $this, "Unable to free MySQL result" ); 00182 } 00183 } 00184 00190 function fetchObject( $res ) { 00191 if ( $res instanceof ResultWrapper ) { 00192 $res = $res->result; 00193 } 00194 wfSuppressWarnings(); 00195 $row = mysql_fetch_object( $res ); 00196 wfRestoreWarnings(); 00197 if( $this->lastErrno() ) { 00198 throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); 00199 } 00200 return $row; 00201 } 00202 00208 function fetchRow( $res ) { 00209 if ( $res instanceof ResultWrapper ) { 00210 $res = $res->result; 00211 } 00212 wfSuppressWarnings(); 00213 $row = mysql_fetch_array( $res ); 00214 wfRestoreWarnings(); 00215 if ( $this->lastErrno() ) { 00216 throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) ); 00217 } 00218 return $row; 00219 } 00220 00226 function numRows( $res ) { 00227 if ( $res instanceof ResultWrapper ) { 00228 $res = $res->result; 00229 } 00230 wfSuppressWarnings(); 00231 $n = mysql_num_rows( $res ); 00232 wfRestoreWarnings(); 00233 if( $this->lastErrno() ) { 00234 throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) ); 00235 } 00236 return $n; 00237 } 00238 00243 function numFields( $res ) { 00244 if ( $res instanceof ResultWrapper ) { 00245 $res = $res->result; 00246 } 00247 return mysql_num_fields( $res ); 00248 } 00249 00255 function fieldName( $res, $n ) { 00256 if ( $res instanceof ResultWrapper ) { 00257 $res = $res->result; 00258 } 00259 return mysql_field_name( $res, $n ); 00260 } 00261 00265 function insertId() { 00266 return mysql_insert_id( $this->mConn ); 00267 } 00268 00274 function dataSeek( $res, $row ) { 00275 if ( $res instanceof ResultWrapper ) { 00276 $res = $res->result; 00277 } 00278 return mysql_data_seek( $res, $row ); 00279 } 00280 00284 function lastErrno() { 00285 if ( $this->mConn ) { 00286 return mysql_errno( $this->mConn ); 00287 } else { 00288 return mysql_errno(); 00289 } 00290 } 00291 00295 function lastError() { 00296 if ( $this->mConn ) { 00297 # Even if it's non-zero, it can still be invalid 00298 wfSuppressWarnings(); 00299 $error = mysql_error( $this->mConn ); 00300 if ( !$error ) { 00301 $error = mysql_error(); 00302 } 00303 wfRestoreWarnings(); 00304 } else { 00305 $error = mysql_error(); 00306 } 00307 if( $error ) { 00308 $error .= ' (' . $this->mServer . ')'; 00309 } 00310 return $error; 00311 } 00312 00316 function affectedRows() { 00317 return mysql_affected_rows( $this->mConn ); 00318 } 00319 00327 function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) { 00328 return $this->nativeReplace( $table, $rows, $fname ); 00329 } 00330 00343 public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) { 00344 $options['EXPLAIN'] = true; 00345 $res = $this->select( $table, $vars, $conds, $fname, $options ); 00346 if ( $res === false ) { 00347 return false; 00348 } 00349 if ( !$this->numRows( $res ) ) { 00350 return 0; 00351 } 00352 00353 $rows = 1; 00354 foreach ( $res as $plan ) { 00355 $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero 00356 } 00357 return $rows; 00358 } 00359 00365 function fieldInfo( $table, $field ) { 00366 $table = $this->tableName( $table ); 00367 $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true ); 00368 if ( !$res ) { 00369 return false; 00370 } 00371 $n = mysql_num_fields( $res->result ); 00372 for( $i = 0; $i < $n; $i++ ) { 00373 $meta = mysql_fetch_field( $res->result, $i ); 00374 if( $field == $meta->name ) { 00375 return new MySQLField($meta); 00376 } 00377 } 00378 return false; 00379 } 00380 00390 function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) { 00391 # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not. 00392 # SHOW INDEX should work for 3.x and up: 00393 # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html 00394 $table = $this->tableName( $table ); 00395 $index = $this->indexName( $index ); 00396 $sql = 'SHOW INDEX FROM ' . $table; 00397 $res = $this->query( $sql, $fname ); 00398 00399 if ( !$res ) { 00400 return null; 00401 } 00402 00403 $result = array(); 00404 00405 foreach ( $res as $row ) { 00406 if ( $row->Key_name == $index ) { 00407 $result[] = $row; 00408 } 00409 } 00410 00411 return empty( $result ) ? false : $result; 00412 } 00413 00418 function selectDB( $db ) { 00419 $this->mDBname = $db; 00420 return mysql_select_db( $db, $this->mConn ); 00421 } 00422 00428 function strencode( $s ) { 00429 $sQuoted = mysql_real_escape_string( $s, $this->mConn ); 00430 00431 if($sQuoted === false) { 00432 $this->ping(); 00433 $sQuoted = mysql_real_escape_string( $s, $this->mConn ); 00434 } 00435 return $sQuoted; 00436 } 00437 00445 public function addIdentifierQuotes( $s ) { 00446 return "`" . $this->strencode( $s ) . "`"; 00447 } 00448 00453 public function isQuotedIdentifier( $name ) { 00454 return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`'; 00455 } 00456 00460 function ping() { 00461 $ping = mysql_ping( $this->mConn ); 00462 if ( $ping ) { 00463 return true; 00464 } 00465 00466 mysql_close( $this->mConn ); 00467 $this->mOpened = false; 00468 $this->mConn = false; 00469 $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname ); 00470 return true; 00471 } 00472 00480 function getLag() { 00481 if ( !is_null( $this->mFakeSlaveLag ) ) { 00482 wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" ); 00483 return $this->mFakeSlaveLag; 00484 } 00485 00486 return $this->getLagFromSlaveStatus(); 00487 } 00488 00492 function getLagFromSlaveStatus() { 00493 $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ ); 00494 if ( !$res ) { 00495 return false; 00496 } 00497 $row = $res->fetchObject(); 00498 if ( !$row ) { 00499 return false; 00500 } 00501 if ( strval( $row->Seconds_Behind_Master ) === '' ) { 00502 return false; 00503 } else { 00504 return intval( $row->Seconds_Behind_Master ); 00505 } 00506 } 00507 00513 function getLagFromProcesslist() { 00514 wfDeprecated( __METHOD__, '1.19' ); 00515 $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ ); 00516 if( !$res ) { 00517 return false; 00518 } 00519 # Find slave SQL thread 00520 foreach( $res as $row ) { 00521 /* This should work for most situations - when default db 00522 * for thread is not specified, it had no events executed, 00523 * and therefore it doesn't know yet how lagged it is. 00524 * 00525 * Relay log I/O thread does not select databases. 00526 */ 00527 if ( $row->User == 'system user' && 00528 $row->State != 'Waiting for master to send event' && 00529 $row->State != 'Connecting to master' && 00530 $row->State != 'Queueing master event to the relay log' && 00531 $row->State != 'Waiting for master update' && 00532 $row->State != 'Requesting binlog dump' && 00533 $row->State != 'Waiting to reconnect after a failed master event read' && 00534 $row->State != 'Reconnecting after a failed master event read' && 00535 $row->State != 'Registering slave on master' 00536 ) { 00537 # This is it, return the time (except -ve) 00538 if ( $row->Time > 0x7fffffff ) { 00539 return false; 00540 } else { 00541 return $row->Time; 00542 } 00543 } 00544 } 00545 return false; 00546 } 00547 00555 function masterPosWait( DBMasterPos $pos, $timeout ) { 00556 $fname = 'DatabaseBase::masterPosWait'; 00557 wfProfileIn( $fname ); 00558 00559 # Commit any open transactions 00560 if ( $this->mTrxLevel ) { 00561 $this->commit(); 00562 } 00563 00564 if ( !is_null( $this->mFakeSlaveLag ) ) { 00565 $status = parent::masterPosWait( $pos, $timeout ); 00566 wfProfileOut( $fname ); 00567 return $status; 00568 } 00569 00570 # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set 00571 $encFile = $this->addQuotes( $pos->file ); 00572 $encPos = intval( $pos->pos ); 00573 $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)"; 00574 $res = $this->doQuery( $sql ); 00575 00576 if ( $res && $row = $this->fetchRow( $res ) ) { 00577 wfProfileOut( $fname ); 00578 return $row[0]; 00579 } else { 00580 wfProfileOut( $fname ); 00581 return false; 00582 } 00583 } 00584 00590 function getSlavePos() { 00591 if ( !is_null( $this->mFakeSlaveLag ) ) { 00592 return parent::getSlavePos(); 00593 } 00594 00595 $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' ); 00596 $row = $this->fetchObject( $res ); 00597 00598 if ( $row ) { 00599 $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos; 00600 return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos ); 00601 } else { 00602 return false; 00603 } 00604 } 00605 00611 function getMasterPos() { 00612 if ( $this->mFakeMaster ) { 00613 return parent::getMasterPos(); 00614 } 00615 00616 $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' ); 00617 $row = $this->fetchObject( $res ); 00618 00619 if ( $row ) { 00620 return new MySQLMasterPos( $row->File, $row->Position ); 00621 } else { 00622 return false; 00623 } 00624 } 00625 00629 function getServerVersion() { 00630 return mysql_get_server_info( $this->mConn ); 00631 } 00632 00637 function useIndexClause( $index ) { 00638 return "FORCE INDEX (" . $this->indexName( $index ) . ")"; 00639 } 00640 00644 function lowPriorityOption() { 00645 return 'LOW_PRIORITY'; 00646 } 00647 00651 public static function getSoftwareLink() { 00652 return '[http://www.mysql.com/ MySQL]'; 00653 } 00654 00658 function standardSelectDistinct() { 00659 return false; 00660 } 00661 00665 public function setSessionOptions( array $options ) { 00666 if ( isset( $options['connTimeout'] ) ) { 00667 $timeout = (int)$options['connTimeout']; 00668 $this->query( "SET net_read_timeout=$timeout" ); 00669 $this->query( "SET net_write_timeout=$timeout" ); 00670 } 00671 } 00672 00673 public function streamStatementEnd( &$sql, &$newLine ) { 00674 if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) { 00675 preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m ); 00676 $this->delimiter = $m[1]; 00677 $newLine = ''; 00678 } 00679 return parent::streamStatementEnd( $sql, $newLine ); 00680 } 00681 00688 public function lock( $lockName, $method, $timeout = 5 ) { 00689 $lockName = $this->addQuotes( $lockName ); 00690 $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method ); 00691 $row = $this->fetchObject( $result ); 00692 00693 if( $row->lockstatus == 1 ) { 00694 return true; 00695 } else { 00696 wfDebug( __METHOD__." failed to acquire lock\n" ); 00697 return false; 00698 } 00699 } 00700 00707 public function unlock( $lockName, $method ) { 00708 $lockName = $this->addQuotes( $lockName ); 00709 $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method ); 00710 $row = $this->fetchObject( $result ); 00711 return $row->lockstatus; 00712 } 00713 00720 public function lockTables( $read, $write, $method, $lowPriority = true ) { 00721 $items = array(); 00722 00723 foreach( $write as $table ) { 00724 $tbl = $this->tableName( $table ) . 00725 ( $lowPriority ? ' LOW_PRIORITY' : '' ) . 00726 ' WRITE'; 00727 $items[] = $tbl; 00728 } 00729 foreach( $read as $table ) { 00730 $items[] = $this->tableName( $table ) . ' READ'; 00731 } 00732 $sql = "LOCK TABLES " . implode( ',', $items ); 00733 $this->query( $sql, $method ); 00734 } 00735 00739 public function unlockTables( $method ) { 00740 $this->query( "UNLOCK TABLES", $method ); 00741 } 00742 00749 public function getSearchEngine() { 00750 return 'SearchMySQL'; 00751 } 00752 00757 public function setBigSelects( $value = true ) { 00758 if ( $value === 'default' ) { 00759 if ( $this->mDefaultBigSelects === null ) { 00760 # Function hasn't been called before so it must already be set to the default 00761 return; 00762 } else { 00763 $value = $this->mDefaultBigSelects; 00764 } 00765 } elseif ( $this->mDefaultBigSelects === null ) { 00766 $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' ); 00767 } 00768 $encValue = $value ? '1' : '0'; 00769 $this->query( "SET sql_big_selects=$encValue", __METHOD__ ); 00770 } 00771 00782 function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) { 00783 if ( !$conds ) { 00784 throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' ); 00785 } 00786 00787 $delTable = $this->tableName( $delTable ); 00788 $joinTable = $this->tableName( $joinTable ); 00789 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar "; 00790 00791 if ( $conds != '*' ) { 00792 $sql .= ' AND ' . $this->makeList( $conds, LIST_AND ); 00793 } 00794 00795 return $this->query( $sql, $fname ); 00796 } 00797 00803 function getServerUptime() { 00804 $vars = $this->getMysqlStatus( 'Uptime' ); 00805 return (int)$vars['Uptime']; 00806 } 00807 00813 function wasDeadlock() { 00814 return $this->lastErrno() == 1213; 00815 } 00816 00822 function wasLockTimeout() { 00823 return $this->lastErrno() == 1205; 00824 } 00825 00832 function wasErrorReissuable() { 00833 return $this->lastErrno() == 2013 || $this->lastErrno() == 2006; 00834 } 00835 00841 function wasReadOnlyError() { 00842 return $this->lastErrno() == 1223 || 00843 ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false ); 00844 } 00845 00852 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) { 00853 $tmp = $temporary ? 'TEMPORARY ' : ''; 00854 $newName = $this->addIdentifierQuotes( $newName ); 00855 $oldName = $this->addIdentifierQuotes( $oldName ); 00856 $query = "CREATE $tmp TABLE $newName (LIKE $oldName)"; 00857 $this->query( $query, $fname ); 00858 } 00859 00867 function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) { 00868 $result = $this->query( "SHOW TABLES", $fname); 00869 00870 $endArray = array(); 00871 00872 foreach( $result as $table ) { 00873 $vars = get_object_vars($table); 00874 $table = array_pop( $vars ); 00875 00876 if( !$prefix || strpos( $table, $prefix ) === 0 ) { 00877 $endArray[] = $table; 00878 } 00879 } 00880 00881 return $endArray; 00882 } 00883 00889 public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) { 00890 if( !$this->tableExists( $tableName, $fName ) ) { 00891 return false; 00892 } 00893 return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName ); 00894 } 00895 00899 protected function getDefaultSchemaVars() { 00900 $vars = parent::getDefaultSchemaVars(); 00901 $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] ); 00902 $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $vars['wgDBTableOptions'] ); 00903 return $vars; 00904 } 00905 00912 function getMysqlStatus( $which = "%" ) { 00913 $res = $this->query( "SHOW STATUS LIKE '{$which}'" ); 00914 $status = array(); 00915 00916 foreach ( $res as $row ) { 00917 $status[$row->Variable_name] = $row->Value; 00918 } 00919 00920 return $status; 00921 } 00922 00923 } 00924 00930 class Database extends DatabaseMysql {} 00931 00936 class MySQLField implements Field { 00937 private $name, $tablename, $default, $max_length, $nullable, 00938 $is_pk, $is_unique, $is_multiple, $is_key, $type; 00939 00940 function __construct ( $info ) { 00941 $this->name = $info->name; 00942 $this->tablename = $info->table; 00943 $this->default = $info->def; 00944 $this->max_length = $info->max_length; 00945 $this->nullable = !$info->not_null; 00946 $this->is_pk = $info->primary_key; 00947 $this->is_unique = $info->unique_key; 00948 $this->is_multiple = $info->multiple_key; 00949 $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple ); 00950 $this->type = $info->type; 00951 } 00952 00956 function name() { 00957 return $this->name; 00958 } 00959 00963 function tableName() { 00964 return $this->tableName; 00965 } 00966 00970 function type() { 00971 return $this->type; 00972 } 00973 00977 function isNullable() { 00978 return $this->nullable; 00979 } 00980 00981 function defaultValue() { 00982 return $this->default; 00983 } 00984 00988 function isKey() { 00989 return $this->is_key; 00990 } 00991 00995 function isMultipleKey() { 00996 return $this->is_multiple; 00997 } 00998 } 00999 01000 class MySQLMasterPos implements DBMasterPos { 01001 var $file, $pos; 01002 01003 function __construct( $file, $pos ) { 01004 $this->file = $file; 01005 $this->pos = $pos; 01006 } 01007 01008 function __toString() { 01009 return "{$this->file}/{$this->pos}"; 01010 } 01011 }