Database::selectRowCount(): support $join_conds
[lhc/web/wiklou.git] / includes / db / Database.php
index 2c2d37f..1da85d7 100644 (file)
@@ -45,6 +45,9 @@ abstract class DatabaseBase implements IDatabase {
 
        protected $mServer, $mUser, $mPassword, $mDBname;
 
+       /** @var BagOStuff APC cache */
+       protected $srvCache;
+
        /** @var resource Database connection */
        protected $mConn = null;
        protected $mOpened = false;
@@ -96,6 +99,9 @@ abstract class DatabaseBase implements IDatabase {
         */
        private $mTrxTimestamp = null;
 
+       /** @var float Lag estimate at the time of BEGIN */
+       private $mTrxSlaveLag = null;
+
        /**
         * Remembers the function name given for starting the most recent transaction via begin().
         * Used to provide additional context for error reporting.
@@ -479,11 +485,7 @@ abstract class DatabaseBase implements IDatabase {
         *   - DBO_PERSISTENT: use persistant database connection
         */
        public function setFlag( $flag ) {
-               global $wgDebugDBTransactions;
                $this->mFlags |= $flag;
-               if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
-                       wfDebug( "Implicit transactions are now enabled.\n" );
-               }
        }
 
        /**
@@ -498,11 +500,7 @@ abstract class DatabaseBase implements IDatabase {
         *   - DBO_PERSISTENT: use persistant database connection
         */
        public function clearFlag( $flag ) {
-               global $wgDebugDBTransactions;
                $this->mFlags &= ~$flag;
-               if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
-                       wfDebug( "Implicit transactions are now disabled.\n" );
-               }
        }
 
        /**
@@ -607,7 +605,10 @@ abstract class DatabaseBase implements IDatabase {
         * @param array $params Parameters passed from DatabaseBase::factory()
         */
        function __construct( array $params ) {
-               global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
+               global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode;
+
+               $this->mTrxAtomicLevels = new SplStack;
+               $this->srvCache = ObjectCache::newAccelerator( 'hash' );
 
                $server = $params['host'];
                $user = $params['user'];
@@ -622,14 +623,8 @@ abstract class DatabaseBase implements IDatabase {
                if ( $this->mFlags & DBO_DEFAULT ) {
                        if ( $wgCommandLineMode ) {
                                $this->mFlags &= ~DBO_TRX;
-                               if ( $wgDebugDBTransactions ) {
-                                       wfDebug( "Implicit transaction open disabled.\n" );
-                               }
                        } else {
                                $this->mFlags |= DBO_TRX;
-                               if ( $wgDebugDBTransactions ) {
-                                       wfDebug( "Implicit transaction open enabled.\n" );
-                               }
                        }
                }
 
@@ -926,7 +921,7 @@ abstract class DatabaseBase implements IDatabase {
         *     for a successful read query, or false on failure if $tempIgnore set
         */
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
-               global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
+               global $wgUser;
 
                $this->mLastQuery = $sql;
 
@@ -956,9 +951,6 @@ abstract class DatabaseBase implements IDatabase {
                $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
 
                if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
-                       if ( $wgDebugDBTransactions ) {
-                               wfDebug( "Implicit transaction start.\n" );
-                       }
                        $this->begin( __METHOD__ . " ($fname)" );
                        $this->mTrxAutomatic = true;
                }
@@ -990,15 +982,7 @@ abstract class DatabaseBase implements IDatabase {
                }
 
                if ( $this->debug() ) {
-                       static $cnt = 0;
-
-                       $cnt++;
-                       $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
-                               : $commentedSql;
-                       $sqlx = strtr( $sqlx, "\t\n", '  ' );
-
-                       $master = $isMaster ? 'master' : 'slave';
-                       wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
+                       wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $sql ) );
                }
 
                $queryId = MWDebug::query( $sql, $fname, $isMaster );
@@ -1723,19 +1707,21 @@ abstract class DatabaseBase implements IDatabase {
         *
         * Takes the same arguments as DatabaseBase::select().
         *
-        * @param string $table Table name
+        * @since 1.27 Added $join_conds parameter
+        *
+        * @param array|string $tables Table names
         * @param string $vars Unused
         * @param array|string $conds Filters on the table
         * @param string $fname Function name for profiling
         * @param array $options Options for select
+        * @param array $join_conds Join conditions (since 1.27)
         * @return int Row count
-        * @since 1.24
         */
        public function selectRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array()
+               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array()
        ) {
                $rows = 0;
-               $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options );
+               $sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds );
                $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname );
 
                if ( $res ) {
@@ -1754,7 +1740,7 @@ abstract class DatabaseBase implements IDatabase {
         *
         * @return string
         */
-       static function generalizeSQL( $sql ) {
+       protected static function generalizeSQL( $sql ) {
                # This does the same as the regexp below would do, but in such a way
                # as to avoid crashing php on some large strings.
                # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
@@ -3438,14 +3424,14 @@ abstract class DatabaseBase implements IDatabase {
         * @throws DBError
         */
        final public function begin( $fname = __METHOD__ ) {
-               global $wgDebugDBTransactions;
-
                if ( $this->mTrxLevel ) { // implicit commit
                        if ( $this->mTrxAtomicLevels ) {
                                // If the current transaction was an automatic atomic one, then we definitely have
                                // a problem. Same if there is any unclosed atomic level.
-                               throw new DBUnexpectedError( $this,
-                                       "Attempted to start explicit transaction when atomic levels are still open."
+                               $levels = implode( ', ', $this->mTrxAtomicLevels );
+                               throw new DBUnexpectedError(
+                                       $this,
+                                       "Got explicit BEGIN while atomic sections $levels are still open."
                                );
                        } elseif ( !$this->mTrxAutomatic ) {
                                // We want to warn about inadvertently nested begin/commit pairs, but not about
@@ -3460,9 +3446,8 @@ abstract class DatabaseBase implements IDatabase {
                                        ) )
                                );
                        } else {
-                               // if the transaction was automatic and has done write operations,
-                               // log it if $wgDebugDBTransactions is enabled.
-                               if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+                               // if the transaction was automatic and has done write operations
+                               if ( $this->mTrxDoneWrites ) {
                                        wfDebug( "$fname: Automatic transaction with writes in progress" .
                                                " (from {$this->mTrxFname}), performing implicit commit!\n"
                                        );
@@ -3494,6 +3479,11 @@ abstract class DatabaseBase implements IDatabase {
                $this->mTrxPreCommitCallbacks = array();
                $this->mTrxShortId = wfRandomString( 12 );
                $this->mTrxWriteDuration = 0.0;
+               // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
+               // Get an estimate of the slave lag before then, treating estimate staleness
+               // as lag itself just to be safe
+               $status = $this->getApproximateLagStatus();
+               $this->mTrxSlaveLag = $status['lag'] + ( microtime( true ) - $status['since'] );
        }
 
        /**
@@ -3524,9 +3514,10 @@ abstract class DatabaseBase implements IDatabase {
        final public function commit( $fname = __METHOD__, $flush = '' ) {
                if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
                        // There are still atomic sections open. This cannot be ignored
+                       $levels = implode( ', ', $this->mTrxAtomicLevels );
                        throw new DBUnexpectedError(
                                $this,
-                               "Attempted to commit transaction while atomic sections are still open"
+                               "Got COMMIT while atomic sections $levels are still open"
                        );
                }
 
@@ -3770,6 +3761,80 @@ abstract class DatabaseBase implements IDatabase {
                return true;
        }
 
+       /**
+        * Get the slave lag when the current transaction started
+        * or a general lag estimate if not transaction is active
+        *
+        * This is useful when transactions might use snapshot isolation
+        * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+        * is this lag plus transaction duration. If they don't, it is still
+        * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
+        * indication of the staleness of subsequent reads.
+        *
+        * @return array ('lag': seconds, 'since': UNIX timestamp of BEGIN)
+        * @since 1.27
+        */
+       public function getSessionLagStatus() {
+               return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
+       }
+
+       /**
+        * Get the slave lag when the current transaction started
+        *
+        * This is useful when transactions might use snapshot isolation
+        * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+        * is this lag plus transaction duration. If they don't, it is still
+        * safe to be pessimistic. This returns null if there is no transaction.
+        *
+        * @return array|null ('lag': seconds, 'since': UNIX timestamp of BEGIN)
+        * @since 1.27
+        */
+       public function getTransactionLagStatus() {
+               return $this->mTrxLevel
+                       ? array( 'lag' => $this->mTrxSlaveLag, 'since' => $this->trxTimestamp() )
+                       : null;
+       }
+
+       /**
+        * Get a slave lag estimate for this server
+        *
+        * @return array ('lag': seconds, 'since': UNIX timestamp of estimate)
+        * @since 1.27
+        */
+       public function getApproximateLagStatus() {
+               return array(
+                       'lag'   => $this->getLBInfo( 'slave' ) ? $this->getLag() : 0,
+                       'since' => microtime( true )
+               );
+       }
+
+       /**
+        * Merge the result of getSessionLagStatus() for several DBs
+        * using the most pessimistic values to estimate the lag of
+        * any data derived from them in combination
+        *
+        * This is information is useful for caching modules
+        *
+        * @see WANObjectCache::set()
+        * @see WANObjectCache::getWithSetCallback()
+        *
+        * @param IDatabase $db1
+        * @param IDatabase ...
+        * @return array ('lag': highest lag, 'since': lowest estimate UNIX timestamp)
+        * @since 1.27
+        */
+       public static function getCacheSetOptions( IDatabase $db1 ) {
+               $res = array( 'lag' => 0, 'since' => INF );
+               foreach ( func_get_args() as $db ) {
+                       /** @var IDatabase $db */
+                       $status = $db->getSessionLagStatus();
+                       $res['lag'] = max( $res['lag'], $status['lag'] );
+                       $res['since'] = min( $res['since'], $status['since'] );
+               }
+
+               return $res;
+       }
+
        /**
         * Get slave lag. Currently supported only by MySQL.
         *
@@ -4246,3 +4311,11 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 }
+
+/**
+ * @since 1.27
+ */
+abstract class Database extends DatabaseBase {
+       // B/C until nothing type hints for DatabaseBase
+       // @TODO: finish renaming DatabaseBase => Database
+}