Merge "Split out ConvertableTimestamp class"
[lhc/web/wiklou.git] / includes / db / Database.php
index d4c8c22..e908824 100644 (file)
@@ -62,8 +62,10 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        protected $mDBname;
        /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
        protected $tableAliases = [];
-       /** @var bool */
+       /** @var bool Whether this PHP instance is for a CLI script */
        protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
 
        /** @var BagOStuff APC cache */
        protected $srvCache;
@@ -85,7 +87,7 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        protected $mTrxPreCommitCallbacks = [];
        /** @var array[] List of (callable, method name) */
        protected $mTrxEndCallbacks = [];
-       /** @var array[] Map of (name => (callable, method name)) */
+       /** @var callable[] Map of (name => callable) */
        protected $mTrxRecurringCallbacks = [];
        /** @var bool Whether to suppress triggering of transaction end callbacks */
        protected $mTrxEndCallbacksSuppressed = false;
@@ -251,6 +253,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                $this->cliMode = isset( $params['cliMode'] )
                        ? $params['cliMode']
                        : ( PHP_SAPI === 'cli' );
+               $this->agent = isset( $params['agent'] )
+                       ? str_replace( '/', '-', $params['agent'] ) // escape for comment
+                       : '';
 
                $this->mFlags = $flags;
                if ( $this->mFlags & DBO_DEFAULT ) {
@@ -308,8 +313,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         * @throws InvalidArgumentException If the database driver or extension cannot be found
         */
        final public static function factory( $dbType, $p = [] ) {
-               global $wgCommandLineMode;
-
                $canonicalDBTypes = [
                        'mysql' => [ 'mysqli', 'mysql' ],
                        'postgres' => [],
@@ -367,7 +370,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                                $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
                        }
                        $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
-                       $p['cliMode'] = $wgCommandLineMode;
 
                        $conn = new $class( $p );
                        if ( isset( $p['connLogger'] ) ) {
@@ -379,7 +381,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                        if ( isset( $p['errorLogger'] ) ) {
                                $conn->errorLogger = $p['errorLogger'];
                        } else {
-                               $conn->errorLogger = [ MWExceptionHandler::class, 'logException' ];
+                               $conn->errorLogger = function ( Exception $e ) {
+                                       trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+                               };
                        }
                } else {
                        $conn = null;
@@ -410,18 +414,25 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         *   - false to disable debugging
         *   - omitted or null to do nothing
         *
-        * @return bool|null Previous value of the flag
+        * @return bool Previous value of the flag
+        * @deprecated since 1.28; use setFlag()
         */
        public function debug( $debug = null ) {
-               return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
+               $res = $this->getFlag( DBO_DEBUG );
+               if ( $debug !== null ) {
+                       $debug ? $this->setFlag( DBO_DEBUG ) : $this->clearFlag( DBO_DEBUG );
+               }
+
+               return $res;
        }
 
        public function bufferResults( $buffer = null ) {
-               if ( is_null( $buffer ) ) {
-                       return !(bool)( $this->mFlags & DBO_NOBUFFER );
-               } else {
-                       return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
+               $res = !$this->getFlag( DBO_NOBUFFER );
+               if ( $buffer !== null ) {
+                       $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER );
                }
+
+               return $res;
        }
 
        /**
@@ -437,7 +448,12 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         * @return bool The previous value of the flag.
         */
        protected function ignoreErrors( $ignoreErrors = null ) {
-               return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
+               $res = $this->getFlag( DBO_IGNORE );
+               if ( $ignoreErrors !== null ) {
+                       $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE );
+               }
+
+               return $res;
        }
 
        public function trxLevel() {
@@ -449,11 +465,17 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        }
 
        public function tablePrefix( $prefix = null ) {
-               return wfSetVar( $this->mTablePrefix, $prefix );
+               $old = $this->mTablePrefix;
+               $this->mTablePrefix = $prefix;
+
+               return $old;
        }
 
        public function dbSchema( $schema = null ) {
-               return wfSetVar( $this->mSchema, $schema );
+               $old = $this->mSchema;
+               $this->mSchema = $schema;
+
+               return $old;
        }
 
        /**
@@ -618,6 +640,25 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
        }
 
+       protected function pendingWriteAndCallbackCallers() {
+               if ( !$this->mTrxLevel ) {
+                       return [];
+               }
+
+               $fnames = $this->mTrxWriteCallers;
+               foreach ( [
+                       $this->mTrxIdleCallbacks,
+                       $this->mTrxPreCommitCallbacks,
+                       $this->mTrxEndCallbacks
+               ] as $callbacks ) {
+                       foreach ( $callbacks as $callback ) {
+                               $fnames[] = $callback[1];
+                       }
+               }
+
+               return $fnames;
+       }
+
        public function isOpen() {
                return $this->mOpened;
        }
@@ -831,8 +872,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        }
 
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
-               global $wgUser;
-
                $priorWritesPending = $this->writesOrCallbacksPending();
                $this->mLastQuery = $sql;
 
@@ -846,20 +885,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                        $this->mDoneWrites = microtime( true );
                }
 
-               # Add a comment for easy SHOW PROCESSLIST interpretation
-               if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
-                       $userName = $wgUser->getName();
-                       if ( mb_strlen( $userName ) > 15 ) {
-                               $userName = mb_substr( $userName, 0, 15 ) . '...';
-                       }
-                       $userName = str_replace( '/', '', $userName );
-               } else {
-                       $userName = '';
-               }
-
                // Add trace comment to the begin of the sql string, right after the operator.
                // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
-               $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
+               $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
 
                # Start implicit transactions that wrap the request if DBO_TRX is enabled
                if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
@@ -2671,23 +2699,23 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                return false;
        }
 
-       final public function onTransactionResolution( callable $callback ) {
+       final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
                        throw new DBUnexpectedError( $this, "No transaction is active." );
                }
-               $this->mTrxEndCallbacks[] = [ $callback, wfGetCaller() ];
+               $this->mTrxEndCallbacks[] = [ $callback, $fname ];
        }
 
-       final public function onTransactionIdle( callable $callback ) {
-               $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
+       final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
+               $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
                if ( !$this->mTrxLevel ) {
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
                }
        }
 
-       final public function onTransactionPreCommitOrIdle( callable $callback ) {
+       final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
                if ( $this->mTrxLevel ) {
-                       $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
+                       $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
                } else {
                        // If no transaction is active, then make one for this callback
                        $this->startAtomic( __METHOD__ );
@@ -2703,7 +2731,7 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
 
        final public function setTransactionListener( $name, callable $callback = null ) {
                if ( $callback ) {
-                       $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
+                       $this->mTrxRecurringCallbacks[$name] = $callback;
                } else {
                        unset( $this->mTrxRecurringCallbacks[$name] );
                }
@@ -2818,9 +2846,8 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                /** @var Exception $e */
                $e = null; // first exception
 
-               foreach ( $this->mTrxRecurringCallbacks as $callback ) {
+               foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
                        try {
-                               list( $phpCallback ) = $callback;
                                $phpCallback( $trigger, $this );
                        } catch ( Exception $ex ) {
                                call_user_func( $this->errorLogger, $ex );
@@ -2907,7 +2934,7 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
                $this->mTrxAutomaticAtomic = false;
                $this->mTrxAtomicLevels = [];
-               $this->mTrxShortId = wfRandomString( 12 );
+               $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
                $this->mTrxWriteDuration = 0.0;
                $this->mTrxWriteQueryCount = 0;
                $this->mTrxWriteAdjDuration = 0.0;
@@ -3043,9 +3070,10 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        public function flushSnapshot( $fname = __METHOD__ ) {
                if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
                        // This only flushes transactions to clear snapshots, not to write data
+                       $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
                        throw new DBUnexpectedError(
                                $this,
-                               "$fname: Cannot COMMIT to clear snapshot because writes are pending."
+                               "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
                        );
                }
 
@@ -3514,9 +3542,10 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
                if ( $this->writesOrCallbacksPending() ) {
                        // This only flushes transactions to clear snapshots, not to write data
+                       $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
                        throw new DBUnexpectedError(
                                $this,
-                               "$fname: Cannot COMMIT to clear snapshot because writes are pending."
+                               "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
                        );
                }
 
@@ -3529,15 +3558,18 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                                // There is a good chance an exception was thrown, causing any early return
                                // from the caller. Let any error handler get a chance to issue rollback().
                                // If there isn't one, let the error bubble up and trigger server-side rollback.
-                               $this->onTransactionResolution( function () use ( $lockKey, $fname ) {
-                                       $this->unlock( $lockKey, $fname );
-                               } );
+                               $this->onTransactionResolution(
+                                       function () use ( $lockKey, $fname ) {
+                                               $this->unlock( $lockKey, $fname );
+                                       },
+                                       $fname
+                               );
                        } else {
                                $this->unlock( $lockKey, $fname );
                        }
                } );
 
-               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
+               $this->commit( $fname, self::FLUSHING_INTERNAL );
 
                return $unlocker;
        }
@@ -3658,18 +3690,11 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
                        trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
                }
-               $danglingCallbacks = array_merge(
-                       $this->mTrxIdleCallbacks,
-                       $this->mTrxPreCommitCallbacks,
-                       $this->mTrxEndCallbacks
-               );
-               if ( $danglingCallbacks ) {
-                       $callers = [];
-                       foreach ( $danglingCallbacks as $callbackInfo ) {
-                               $callers[] = $callbackInfo[1];
-                       }
-                       $callers = implode( ', ', $callers );
-                       trigger_error( "DB transaction callbacks still pending (from $callers)." );
+
+               $danglingWriters = $this->pendingWriteAndCallbackCallers();
+               if ( $danglingWriters ) {
+                       $fnames = implode( ', ', $danglingWriters );
+                       trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
                }
        }
 }