General database-related code cleanup:
authorTim Starling <tstarling@users.mediawiki.org>
Thu, 23 Jun 2011 03:14:11 +0000 (03:14 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Thu, 23 Jun 2011 03:14:11 +0000 (03:14 +0000)
* Merged the 4 simulated implementations of Database*::replace(). I took diffs, they were nearly identical. I made one based on the IBM DB2 version, since it used insert() which looked like a nice touch.
* Provided the non-simulated implementation of Database*::replace() via a protected member function, and made DatabaseMysql::replace() and DatabaseSqlite::replace() into a wrapper for it.
* Moved the MySQL-specific functionality from masterPosWait(), getSlavePos() and getMasterPos() from DatabaseBase to DatabaseMysql.
* Renamed getStatus() to getMysqlStatus() and moved it to DatabaseMysql. Removed "unimplemented" errors from two other subclasses. Really there's no way another DBMS could or should implement this function.
* Split the LoadMonitor class into a no-op class and a MySQL-specific class, to avoid fatal errors due to the getMysqlStatus() call if other DBMSs tried to use LoadBalancer with multiple servers. Of course there are lots of other reasons it won't work, I'm just fixing the architecture issue here.

And while I have a replicated test setup handy:
* On MySQL 4.1.9 and later, use SHOW SLAVE STATUS to get the lag instead of SHOW PROCESSLIST. This has the advantage of reading zero when there are no events for a while.

12 files changed:
RELEASE-NOTES-1.19
includes/AutoLoader.php
includes/db/Database.php
includes/db/DatabaseIbm_db2.php
includes/db/DatabaseMssql.php
includes/db/DatabaseMysql.php
includes/db/DatabaseOracle.php
includes/db/DatabasePostgres.php
includes/db/DatabaseSqlite.php
includes/db/DatabaseUtility.php
includes/db/LoadBalancer.php
includes/db/LoadMonitor.php

index 664cc70..97b1d34 100644 (file)
@@ -54,6 +54,8 @@ production.
 * (bug 29441) Expose CapitalLinks config in JS to allow modules to properly
   handle titles on case-sensitive wikis.
 * (bug 29397) Implement mw.Title module in core.
+* In MySQL 4.1.9+ with replication enabled, get the slave lag from SHOW SLAVE 
+  STATUS instead of SHOW PROCESSLIST.
 
 === Bug fixes in 1.19 ===
 * (bug 28868) Show total pages in the subtitle of an image on the
index 64ad885..f9a83f7 100644 (file)
@@ -386,6 +386,7 @@ $wgAutoloadLocalClasses = array(
        'DBConnectionError' => 'includes/db/DatabaseError.php',
        'DBError' => 'includes/db/DatabaseError.php',
        'DBObject' => 'includes/db/DatabaseUtility.php',
+       'DBMasterPos' => 'includes/db/DatabaseUtility.php',
        'DBQueryError' => 'includes/db/DatabaseError.php',
        'DBUnexpectedError' => 'includes/db/DatabaseError.php',
        'FakeResultWrapper' => 'includes/db/DatabaseUtility.php',
@@ -401,6 +402,7 @@ $wgAutoloadLocalClasses = array(
        'LoadBalancer_Single' => 'includes/db/LBFactory_Single.php',
        'LoadMonitor' => 'includes/db/LoadMonitor.php',
        'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php',
+       'LoadMonitor_Null' => 'includes/db/LoadMonitor.php',
        'MySQLField' => 'includes/db/DatabaseMysql.php',
        'MySQLMasterPos' => 'includes/db/DatabaseMysql.php',
        'ORAField' => 'includes/db/DatabaseOracle.php',
index 150bb5d..c0ced73 100644 (file)
@@ -2215,15 +2215,24 @@ abstract class DatabaseBase implements DatabaseType {
        }
 
        /**
-        * REPLACE query wrapper
-        * PostgreSQL simulates this with a DELETE followed by INSERT
-        * $row is the row to insert, an associative array
-        * $uniqueIndexes is an array of indexes. Each element may be either a
-        * field name or an array of field names
+        * REPLACE query wrapper.
         *
-        * It may be more efficient to leave off unique indexes which are unlikely to collide.
-        * However if you do this, you run the risk of encountering errors which wouldn't have
-        * occurred in MySQL
+        * REPLACE is a very handy MySQL extension, which functions like an INSERT
+        * except that when there is a duplicate key error, the old row is deleted
+        * and the new row is inserted in its place. 
+        *
+        * We simulate this with standard SQL with a DELETE followed by INSERT. To 
+        * perform the delete, we need to know what the unique indexes are so that 
+        * we know how to find the conflicting rows.
+        *
+        * It may be more efficient to leave off unique indexes which are unlikely 
+        * to collide. However if you do this, you run the risk of encountering 
+        * errors which wouldn't have occurred in MySQL.
+        * 
+        * @param $rows Can be either a single row to insert, or multiple rows, 
+        *    in the same format as for DatabaseBase::insert()
+        * @param $uniqueIndexes is an array of indexes. Each element may be either 
+        *    a field name or an array of field names
         *
         * @param $table String: The table to replace the row(s) in.
         * @param $uniqueIndexes Array: An associative array of indexes
@@ -2231,6 +2240,61 @@ abstract class DatabaseBase implements DatabaseType {
         * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
         */
        function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
+               $quotedTable = $this->tableName( $table );
+
+               if ( count( $rows ) == 0 ) {
+                       return;
+               }
+
+               # Single row case
+               if ( !is_array( reset( $rows ) ) ) {
+                       $rows = array( $rows );
+               }
+
+               foreach( $rows as $row ) {
+                       # Delete rows which collide
+                       if ( $uniqueIndexes ) {
+                               $sql = "DELETE FROM $quotedTable WHERE ";
+                               $first = true;
+                               foreach ( $uniqueIndexes as $index ) {
+                                       if ( $first ) {
+                                               $first = false;
+                                               $sql .= '( ';
+                                       } else {
+                                               $sql .= ' ) OR ( ';
+                                       }
+                                       if ( is_array( $index ) ) {
+                                               $first2 = true;
+                                               foreach ( $index as $col ) {
+                                                       if ( $first2 ) {
+                                                               $first2 = false;
+                                                       } else {
+                                                               $sql .= ' AND ';
+                                                       }
+                                                       $sql .= $col . '=' . $this->addQuotes( $row[$col] );
+                                               }
+                                       } else {
+                                               $sql .= $index . '=' . $this->addQuotes( $row[$index] );
+                                       }
+                               }
+                               $sql .= ' )';
+                               $this->query( $sql, $fname );
+                       }
+
+                       # Now insert the row
+                       $this->insert( $table, $row );
+               }
+       }
+
+       /**
+        * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
+        * statement.
+        *
+        * @param $table Table name
+        * @param $rows Rows to insert
+        * @param $fname Caller function name
+        */
+       protected function nativeReplace( $table, $rows, $fname ) {
                $table = $this->tableName( $table );
 
                # Single row case
@@ -2584,20 +2648,20 @@ abstract class DatabaseBase implements DatabaseType {
        }
 
        /**
-        * Do a SELECT MASTER_POS_WAIT()
+        * Wait for the slave to catch up to a given master position.
+        *
+        * @param $pos DBMasterPos object
+        * @param $timeout Integer: the maximum number of seconds to wait for 
+        *   synchronisation
         *
-        * @param $pos MySQLMasterPos object
-        * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
+        * @return An integer: zero if the slave was past that position already,
+        *   greater than zero if we waited for some period of time, less than 
+        *   zero if we timed out.
         */
-       function masterPosWait( MySQLMasterPos $pos, $timeout ) {
+       function masterPosWait( DBMasterPos $pos, $timeout ) {
                $fname = 'DatabaseBase::masterPosWait';
                wfProfileIn( $fname );
 
-               # Commit any open transactions
-               if ( $this->mTrxLevel ) {
-                       $this->commit();
-               }
-
                if ( !is_null( $this->mFakeSlaveLag ) ) {
                        $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
 
@@ -2617,59 +2681,36 @@ abstract class DatabaseBase implements DatabaseType {
                        }
                }
 
-               # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
-               $encFile = $this->addQuotes( $pos->file );
-               $encPos = intval( $pos->pos );
-               $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
-               $res = $this->doQuery( $sql );
+               wfProfileOut( $fname );
 
-               if ( $res && $row = $this->fetchRow( $res ) ) {
-                       wfProfileOut( $fname );
-                       return $row[0];
-               } else {
-                       wfProfileOut( $fname );
-                       return false;
-               }
+               # Real waits are implemented in the subclass.
+               return 0;
        }
 
        /**
-        * Get the position of the master from SHOW SLAVE STATUS
+        * Get the replication position of this slave
         *
-        * @return MySQLMasterPos|false
+        * @return DBMasterPos, or false if this is not a slave.
         */
        function getSlavePos() {
                if ( !is_null( $this->mFakeSlaveLag ) ) {
                        $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
                        wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
                        return $pos;
-               }
-
-               $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
-               $row = $this->fetchObject( $res );
-
-               if ( $row ) {
-                       $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
-                       return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
                } else {
+                       # Stub
                        return false;
                }
        }
 
        /**
-        * Get the position of the master from SHOW MASTER STATUS
+        * Get the position of this master
         *
-        * @return MySQLMasterPos|false
+        * @return DBMasterPos, or false if this is not a master
         */
        function getMasterPos() {
                if ( $this->mFakeMaster ) {
                        return new MySQLMasterPos( 'fake', microtime( true ) );
-               }
-
-               $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
-               $row = $this->fetchObject( $res );
-
-               if ( $row ) {
-                       return new MySQLMasterPos( $row->File, $row->Position );
                } else {
                        return false;
                }
@@ -2817,22 +2858,6 @@ abstract class DatabaseBase implements DatabaseType {
                return intval( $this->mFakeSlaveLag );
        }
 
-       /**
-        * Get status information from SHOW STATUS in an associative array
-        *
-        * @return array
-        */
-       function getStatus( $which = "%" ) {
-               $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
-               $status = array();
-
-               foreach ( $res as $row ) {
-                       $status[$row->Variable_name] = $row->Value;
-               }
-
-               return $status;
-       }
-
        /**
         * Return the maximum number of items allowed in a list, or 0 for unlimited.
         *
index 77d8d76..661b892 100644 (file)
@@ -554,45 +554,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
                return false;
        }
 
-       /**
-        * Create tables, stored procedures, and so on
-        */
-       public function setup_database() {
-               try {
-                       // TODO: switch to root login if available
-
-                       // Switch into the correct namespace
-                       $this->applySchema();
-                       $this->begin();
-
-                       $res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" );
-                       if ( $res !== true ) {
-                               print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>';
-                       } else {
-                               print ' done</li>';
-                       }
-                       $res = $this->sourceFile( "../maintenance/ibm_db2/foreignkeys.sql" );
-                       if ( $res !== true ) {
-                               print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>';
-                       } else {
-                               print '<li>Foreign keys done</li>';
-                       }
-
-                       // TODO: populate interwiki links
-
-                       if ( $this->lastError() ) {
-                               $this->installPrint(
-                                       'Errors encountered during table creation -- rolled back' );
-                               $this->installPrint( 'Please install again' );
-                               $this->rollback();
-                       } else {
-                               $this->commit();
-                       }
-               } catch ( MWException $mwe ) {
-                       print "<br><pre>$mwe</pre><br>";
-               }
-       }
-
        /**
         * Escapes strings
         * Doesn't escape numbers
@@ -1067,63 +1028,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
                return db2_num_rows( $this->mLastResult );
        }
 
-       /**
-        * Simulates REPLACE with a DELETE followed by INSERT
-        * @param $table Object
-        * @param $uniqueIndexes Array consisting of indexes and arrays of indexes
-        * @param $rows Array: rows to insert
-        * @param $fname String: name of the function for profiling
-        * @return nothing
-        */
-       function replace( $table, $uniqueIndexes, $rows,
-               $fname = 'DatabaseIbm_db2::replace' )
-       {
-               $table = $this->tableName( $table );
-
-               if ( count( $rows )==0 ) {
-                       return;
-               }
-
-               # Single row case
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = array( $rows );
-               }
-
-               foreach( $rows as $row ) {
-                       # Delete rows which collide
-                       if ( $uniqueIndexes ) {
-                               $sql = "DELETE FROM $table WHERE ";
-                               $first = true;
-                               foreach ( $uniqueIndexes as $index ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                               $sql .= '( ';
-                                       } else {
-                                               $sql .= ' ) OR ( ';
-                                       }
-                                       if ( is_array( $index ) ) {
-                                               $first2 = true;
-                                               foreach ( $index as $col ) {
-                                                       if ( $first2 ) {
-                                                               $first2 = false;
-                                                       } else {
-                                                               $sql .= ' AND ';
-                                                       }
-                                                       $sql .= $col . '=' . $this->addQuotes( $row[$col] );
-                                               }
-                                       } else {
-                                               $sql .= $index . '=' . $this->addQuotes( $row[$index] );
-                                       }
-                               }
-                               $sql .= ' )';
-                               $this->query( $sql, $fname );
-                       }
-
-                       # Now insert the row
-                       $this->insert($table, $row);
-               }
-       }
-
        /**
         * Returns the number of rows in the result set
         * Has to be called right after the corresponding select query
@@ -1345,14 +1249,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
        ######################################
        # Unimplemented and not applicable
        ######################################
-       /**
-        * Not implemented
-        * @return string ''
-        */
-       public function getStatus( $which = '%' ) {
-               $this->installPrint( 'Not implemented for DB2: getStatus()' );
-               return '';
-       }
        /**
         * Not implemented
         * @return string $sql
index 5cb14fb..cbdf89c 100644 (file)
@@ -539,65 +539,6 @@ class DatabaseMssql extends DatabaseBase {
                }
        }
 
-
-       # REPLACE query wrapper
-       # MSSQL simulates this with a DELETE followed by INSERT
-       # $row is the row to insert, an associative array
-       # $uniqueIndexes is an array of indexes. Each element may be either a
-       # field name or an array of field names
-       #
-       # It may be more efficient to leave off unique indexes which are unlikely to collide.
-       # However if you do this, you run the risk of encountering errors which wouldn't have
-       # occurred in MySQL
-       function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMssql::replace' ) {
-               $table = $this->tableName( $table );
-
-               if ( count( $rows ) == 0 ) {
-                       return;
-               }
-
-               # Single row case
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = array( $rows );
-               }
-
-               foreach ( $rows as $row ) {
-                       # Delete rows which collide
-                       if ( $uniqueIndexes ) {
-                               $sql = "DELETE FROM $table WHERE ";
-                               $first = true;
-                               foreach ( $uniqueIndexes as $index ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                               $sql .= "(";
-                                       } else {
-                                               $sql .= ') OR (';
-                                       }
-                                       if ( is_array( $index ) ) {
-                                               $first2 = true;
-                                               foreach ( $index as $col ) {
-                                                       if ( $first2 ) {
-                                                               $first2 = false;
-                                                       } else {
-                                                               $sql .= ' AND ';
-                                                       }
-                                                       $sql .= $col . '=' . $this->addQuotes( $row[$col] );
-                                               }
-                                       } else {
-                                               $sql .= $index . '=' . $this->addQuotes( $row[$index] );
-                                       }
-                               }
-                               $sql .= ')';
-                               $this->query( $sql, $fname );
-                       }
-
-                       # Now insert the row
-                       $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) . ') VALUES (' .
-                               $this->makeList( $row, LIST_COMMA ) . ')';
-                       $this->query( $sql, $fname );
-               }
-       }
-
        # Returns the size of a text field, or -1 for "unlimited"
        function textFieldSize( $table, $field ) {
                $table = $this->tableName( $table );
@@ -762,48 +703,6 @@ class DatabaseMssql extends DatabaseBase {
                $this->mTrxLevel = 0;
        }
 
-       function setup_database() {
-               global $wgDBuser;
-
-               // Make sure that we can write to the correct schema
-               $ctest = "mediawiki_test_table";
-               if ( $this->tableExists( $ctest ) ) {
-                       $this->doQuery( "DROP TABLE $ctest" );
-               }
-               $SQL = "CREATE TABLE $ctest (a int)";
-               $res = $this->doQuery( $SQL );
-               if ( !$res ) {
-                       print "<b>FAILED</b>. Make sure that the user " . htmlspecialchars( $wgDBuser ) . " can write to the database</li>\n";
-                       die();
-               }
-               $this->doQuery( "DROP TABLE $ctest" );
-
-               $res = $this->sourceFile( "../maintenance/mssql/tables.sql" );
-               if ( $res !== true ) {
-                       echo " <b>FAILED</b></li>";
-                       die( htmlspecialchars( $res ) );
-               }
-
-               # Avoid the non-standard "REPLACE INTO" syntax
-               $f = fopen( "../maintenance/interwiki.sql", 'r' );
-               if ( $f == false ) {
-                       die( "<li>Could not find the interwiki.sql file" );
-               }
-               # We simply assume it is already empty as we have just created it
-               $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
-               while ( ! feof( $f ) ) {
-                       $line = fgets( $f, 1024 );
-                       $matches = array();
-                       if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) {
-                               continue;
-                       }
-                       $this->query( "$SQL $matches[1],$matches[2])" );
-               }
-               print " (table interwiki successfully populated)...\n";
-
-               $this->commit();
-       }
-
        /**
         * Escapes a identifier for use inm SQL.
         * Throws an exception if it is invalid.
index 4f46a3f..90d2dc9 100644 (file)
@@ -241,6 +241,10 @@ class DatabaseMysql extends DatabaseBase {
 
        function affectedRows() { return mysql_affected_rows( $this->mConn ); }
 
+       function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) {
+               return $this->nativeReplace( $table, $rows, $fname );
+       }
+
        /**
         * Estimate rows in dataset
         * Returns estimated count, based on EXPLAIN output
@@ -348,7 +352,11 @@ class DatabaseMysql extends DatabaseBase {
 
        /**
         * Returns slave lag.
-        * At the moment, this will only work if the DB user has the PROCESS privilege
+        *
+        * On MySQL 4.1.9 and later, this will do a SHOW SLAVE STATUS. On earlier
+        * versions of MySQL, it uses SHOW PROCESSLIST, which requires the PROCESS
+        * privilege.
+        *
         * @result int
         */
        function getLag() {
@@ -356,6 +364,31 @@ class DatabaseMysql extends DatabaseBase {
                        wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
                        return $this->mFakeSlaveLag;
                }
+
+               if ( version_compare( $this->getServerVersion(), '4.1.9', '>=' ) ) {
+                       return $this->getLagFromSlaveStatus();
+               } else {
+                       return $this->getLagFromProcesslist();
+               }
+       }
+
+       function getLagFromSlaveStatus() {
+               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+               if ( !$res ) {
+                       return false;
+               }
+               $row = $res->fetchObject();
+               if ( !$row ) {
+                       return false;
+               }
+               if ( strval( $row->Seconds_Behind_Master ) === '' ) {
+                       return false;
+               } else {
+                       return intval( $row->Seconds_Behind_Master );
+               }
+       }
+
+       function getLagFromProcesslist() {
                $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
                if( !$res ) {
                        return false;
@@ -388,6 +421,83 @@ class DatabaseMysql extends DatabaseBase {
                }
                return false;
        }
+       
+       /**
+        * Wait for the slave to catch up to a given master position.
+        *
+        * @param $pos DBMasterPos object
+        * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
+        */
+       function masterPosWait( DBMasterPos $pos, $timeout ) {
+               $fname = 'DatabaseBase::masterPosWait';
+               wfProfileIn( $fname );
+
+               # Commit any open transactions
+               if ( $this->mTrxLevel ) {
+                       $this->commit();
+               }
+
+               if ( !is_null( $this->mFakeSlaveLag ) ) {
+                       $status = parent::masterPosWait( $pos, $timeout );
+                       wfProfileOut( $fname );
+                       return $status;
+               }
+
+               # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
+               $encFile = $this->addQuotes( $pos->file );
+               $encPos = intval( $pos->pos );
+               $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
+               $res = $this->doQuery( $sql );
+
+               if ( $res && $row = $this->fetchRow( $res ) ) {
+                       wfProfileOut( $fname );
+                       return $row[0];
+               } else {
+                       wfProfileOut( $fname );
+                       return false;
+               }
+       }
+
+       /**
+        * Get the position of the master from SHOW SLAVE STATUS
+        *
+        * @return MySQLMasterPos|false
+        */
+       function getSlavePos() {
+               if ( !is_null( $this->mFakeSlaveLag ) ) {
+                       return parent::getSlavePos();
+               }
+
+               $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
+               $row = $this->fetchObject( $res );
+
+               if ( $row ) {
+                       $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
+                       return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Get the position of the master from SHOW MASTER STATUS
+        *
+        * @return MySQLMasterPos|false
+        */
+       function getMasterPos() {
+               if ( $this->mFakeMaster ) {
+                       return parent::getMasterPos();
+               }
+
+               $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
+               $row = $this->fetchObject( $res );
+
+               if ( $row ) {
+                       return new MySQLMasterPos( $row->File, $row->Position );
+               } else {
+                       return false;
+               }
+       }
 
        function getServerVersion() {
                return mysql_get_server_info( $this->mConn );
@@ -587,6 +697,23 @@ class DatabaseMysql extends DatabaseBase {
                $vars['wgDBTableOptions'] = $GLOBALS['wgDBTableOptions'];
                return $vars;
        }
+
+       /**
+        * Get status information from SHOW STATUS in an associative array
+        *
+        * @return array
+        */
+       function getMysqlStatus( $which = "%" ) {
+               $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+               $status = array();
+
+               foreach ( $res as $row ) {
+                       $status[$row->Variable_name] = $row->Value;
+               }
+
+               return $status;
+       }
+
 }
 
 /**
@@ -644,7 +771,7 @@ class MySQLField implements Field {
        }
 }
 
-class MySQLMasterPos {
+class MySQLMasterPos implements DBMasterPos {
        var $file, $pos;
 
        function __construct( $file, $pos ) {
index e3a1823..fea8ec9 100644 (file)
@@ -707,66 +707,6 @@ class DatabaseOracle extends DatabaseBase {
                return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
        }
 
-       /**
-        * REPLACE query wrapper
-        * Oracle simulates this with a DELETE followed by INSERT
-        * $row is the row to insert, an associative array
-        * $uniqueIndexes is an array of indexes. Each element may be either a
-        * field name or an array of field names
-        *
-        * It may be more efficient to leave off unique indexes which are unlikely to collide.
-        * However if you do this, you run the risk of encountering errors which wouldn't have
-        * occurred in MySQL.
-        *
-        * @param $table String: table name
-        * @param $uniqueIndexes Array: array of indexes. Each element may be
-        *                       either a field name or an array of field names
-        * @param $rows Array: rows to insert to $table
-        * @param $fname String: function name, you can use __METHOD__ here
-        */
-       function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseOracle::replace' ) {
-               $table = $this->tableName( $table );
-
-               if ( count( $rows ) == 0 ) {
-                       return;
-               }
-
-               # Single row case
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = array( $rows );
-               }
-
-               $sequenceData = $this->getSequenceData( $table );
-
-               foreach ( $rows as $row ) {
-                       # Delete rows which collide
-                       if ( $uniqueIndexes ) {
-                               $deleteConds = array();
-                               foreach ( $uniqueIndexes as $key=>$index ) {
-                                       if ( is_array( $index ) ) {
-                                               $deleteConds2 = array();
-                                               foreach ( $index as $col ) {
-                                                       $deleteConds2[$col] = $row[$col];
-                                               }
-                                               $deleteConds[$key] = $this->makeList( $deleteConds2, LIST_AND );
-                                       } else {
-                                               $deleteConds[$index] = $row[$index];
-                                       }
-                               }
-                               $deleteConds = array( $this->makeList( $deleteConds, LIST_OR ) );
-                               $this->delete( $table, $deleteConds, $fname );
-                       }
-
-
-                       if ( $sequenceData !== false && !isset( $row[$sequenceData['column']] ) ) {
-                               $row[$sequenceData['column']] = $this->nextSequenceValue( $sequenceData['sequence'] );
-                       }
-
-                       # Now insert the row
-                       $this->insert( $table, $row, $fname );
-               }
-       }
-
        # Returns the size of a text field, or -1 for "unlimited"
        function textFieldSize( $table, $field ) {
                $fieldInfoData = $this->fieldInfo( $table, $field );
index adc12de..74316cf 100644 (file)
@@ -647,66 +647,6 @@ class DatabasePostgres extends DatabaseBase {
                return $currval;
        }
 
-       /**
-        * REPLACE query wrapper
-        * Postgres simulates this with a DELETE followed by INSERT
-        * $row is the row to insert, an associative array
-        * $uniqueIndexes is an array of indexes. Each element may be either a
-        * field name or an array of field names
-        *
-        * It may be more efficient to leave off unique indexes which are unlikely to collide.
-        * However if you do this, you run the risk of encountering errors which wouldn't have
-        * occurred in MySQL
-        */
-       function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) {
-               $table = $this->tableName( $table );
-
-               if ( count( $rows ) == 0 ) {
-                       return;
-               }
-
-               # Single row case
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = array( $rows );
-               }
-
-               foreach( $rows as $row ) {
-                       # Delete rows which collide
-                       if ( $uniqueIndexes ) {
-                               $sql = "DELETE FROM $table WHERE ";
-                               $first = true;
-                               foreach ( $uniqueIndexes as $index ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                               $sql .= '(';
-                                       } else {
-                                               $sql .= ') OR (';
-                                       }
-                                       if ( is_array( $index ) ) {
-                                               $first2 = true;
-                                               foreach ( $index as $col ) {
-                                                       if ( $first2 ) {
-                                                               $first2 = false;
-                                                       } else {
-                                                               $sql .= ' AND ';
-                                                       }
-                                                       $sql .= $col.'=' . $this->addQuotes( $row[$col] );
-                                               }
-                                       } else {
-                                               $sql .= $index.'=' . $this->addQuotes( $row[$index] );
-                                       }
-                               }
-                               $sql .= ')';
-                               $this->query( $sql, $fname );
-                       }
-
-                       # Now insert the row
-                       $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
-                               $this->makeList( $row, LIST_COMMA ) . ')';
-                       $this->query( $sql, $fname );
-               }
-       }
-
        # Returns the size of a text field, or -1 for "unlimited"
        function textFieldSize( $table, $field ) {
                $table = $this->tableName( $table );
index 599e80a..92525d7 100644 (file)
@@ -532,12 +532,12 @@ class DatabaseSqlite extends DatabaseBase {
                if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
                        $ret = true;
                        foreach ( $rows as $v ) {
-                               if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) ) {
+                               if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
                                        $ret = false;
                                }
                        }
                } else {
-                       $ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" );
+                       $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
                }
 
                return $ret;
index 7d02a43..d1bced6 100644 (file)
@@ -259,3 +259,10 @@ class LikeMatch {
                return $this->str;
        }
 }
+
+/**
+ * An object representing a master or slave position in a replicated setup.
+ */
+interface DBMasterPos {
+}
+
index 10fbf9a..069b8e1 100644 (file)
@@ -50,8 +50,17 @@ class LoadBalancer {
                $this->mLaggedSlaveMode = false;
                $this->mErrorConnection = false;
                $this->mAllowLagged = false;
-               $this->mLoadMonitorClass = isset( $params['loadMonitor'] )
-                       ? $params['loadMonitor'] : 'LoadMonitor_MySQL';
+
+               if ( isset( $params['loadMonitor'] ) ) {
+                       $this->mLoadMonitorClass = $params['loadMonitor'];
+               } else {
+                       $master = reset( $params['servers'] );
+                       if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
+                               $this->mLoadMonitorClass = 'LoadMonitor_MySQL';
+                       } else {
+                               $this->mLoadMonitorClass = 'LoadMonitor_Null';
+                       }
+               }
 
                foreach( $params['servers'] as $i => $server ) {
                        $this->mLoads[$i] = $server['load'];
index 4bfbe94..f50a267 100644 (file)
@@ -56,6 +56,21 @@ interface LoadMonitor {
        function getLagTimes( $serverIndexes, $wiki );
 }
 
+class LoadMonitor_Null implements LoadMonitor {
+       function __construct( $parent ) {
+       }
+
+       function scaleLoads( &$loads, $group = false, $wiki = false ) {
+       }
+
+       function postConnectionBackoff( $conn, $threshold ) {
+       }
+
+       function getLagTimes( $serverIndexes, $wiki ) {
+               return array_fill_keys( $serverIndexes, 0 );
+       }
+}
+
 
 /**
  * Basic MySQL load monitor with no external dependencies
@@ -150,7 +165,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
                if ( !$threshold ) {
                        return 0;
                }
-               $status = $conn->getStatus("Thread%");
+               $status = $conn->getMysqlStatus("Thread%");
                if ( $status['Threads_running'] > $threshold ) {
                        return $status['Threads_connected'];
                } else {