Merge "Split out ConvertableTimestamp class"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 16 Sep 2016 03:07:28 +0000 (03:07 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 16 Sep 2016 03:07:28 +0000 (03:07 +0000)
1  2 
autoload.php
includes/db/Database.php

diff --combined autoload.php
@@@ -153,7 -153,6 +153,7 @@@ $wgAutoloadLocalClasses = 
        'AtomFeed' => __DIR__ . '/includes/Feed.php',
        'AtomicSectionUpdate' => __DIR__ . '/includes/deferred/AtomicSectionUpdate.php',
        'AttachLatest' => __DIR__ . '/maintenance/attachLatest.php',
 +      'AugmentPageProps' => __DIR__ . '/includes/search/AugmentPageProps.php',
        'AuthManagerSpecialPage' => __DIR__ . '/includes/specialpage/AuthManagerSpecialPage.php',
        'AuthPlugin' => __DIR__ . '/includes/AuthPlugin.php',
        'AuthPluginUser' => __DIR__ . '/includes/AuthPlugin.php',
        'ConvertExtensionToRegistration' => __DIR__ . '/maintenance/convertExtensionToRegistration.php',
        'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
        'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
+       'ConvertableTimestamp' => __DIR__ . '/includes/libs/time/ConvertableTimestamp.php',
        'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
        'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
        'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
        'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php',
        'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
        'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
 +      'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
        'IP' => __DIR__ . '/includes/utils/IP.php',
        'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
        'IPTC' => __DIR__ . '/includes/media/IPTC.php',
        'PatrolLog' => __DIR__ . '/includes/logging/PatrolLog.php',
        'PatrolLogFormatter' => __DIR__ . '/includes/logging/PatrolLogFormatter.php',
        'Pbkdf2Password' => __DIR__ . '/includes/password/Pbkdf2Password.php',
 +      'PerRowAugmentor' => __DIR__ . '/includes/search/PerRowAugmentor.php',
        'PermissionsError' => __DIR__ . '/includes/exception/PermissionsError.php',
        'PhpHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
        'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php',
        'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php',
        'RestbaseVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/RestbaseVirtualRESTService.php',
 +      'ResultAugmentor' => __DIR__ . '/includes/search/ResultAugmentor.php',
 +      'ResultSetAugmentor' => __DIR__ . '/includes/search/ResultSetAugmentor.php',
        'ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
        'RevDelArchiveItem' => __DIR__ . '/includes/revisiondelete/RevDelArchiveItem.php',
        'RevDelArchiveList' => __DIR__ . '/includes/revisiondelete/RevDelArchiveList.php',
        'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
        'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php',
        'TiffHandler' => __DIR__ . '/includes/media/Tiff.php',
-       'TimestampException' => __DIR__ . '/includes/exception/TimestampException.php',
+       'TimestampException' => __DIR__ . '/includes/libs/time/TimestampException.php',
        'Timing' => __DIR__ . '/includes/libs/Timing.php',
        'Title' => __DIR__ . '/includes/Title.php',
        'TitleArray' => __DIR__ . '/includes/TitleArray.php',
diff --combined includes/db/Database.php
@@@ -62,10 -62,8 +62,10 @@@ abstract class DatabaseBase implements 
        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;
@@@ -87,7 -85,7 +87,7 @@@
        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;
                $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 ) {
         * @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' => [],
                                $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'] ) ) {
                        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;
         *   - 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;
        }
  
        /**
         * @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() {
        }
  
        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;
        }
  
        /**
                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;
        }
        }
  
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
 -              global $wgUser;
 -
                $priorWritesPending = $this->writesOrCallbacksPending();
                $this->mLastQuery = $sql;
  
                        $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 )
                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__ );
  
        final public function setTransactionListener( $name, callable $callback = null ) {
                if ( $callback ) {
 -                      $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
 +                      $this->mTrxRecurringCallbacks[$name] = $callback;
                } else {
                        unset( $this->mTrxRecurringCallbacks[$name] );
                }
                /** @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 );
                $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;
        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)."
                        );
                }
  
        }
  
        public function timestamp( $ts = 0 ) {
-               return wfTimestamp( TS_MW, $ts );
+               $t = new ConvertableTimestamp( $ts );
+               // Let errors bubble up to avoid putting garbage in the DB
+               return $t->getTimestamp( TS_MW );
        }
  
        public function timestampOrNull( $ts = null ) {
        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)."
                        );
                }
  
                                // 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;
        }
        }
  
        public function decodeExpiry( $expiry, $format = TS_MW ) {
-               return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
-                       ? 'infinity'
-                       : wfTimestamp( $format, $expiry );
+               if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
+                       return 'infinity';
+               }
+               try {
+                       $t = new ConvertableTimestamp( $expiry );
+                       return $t->getTimestamp( $format );
+               } catch ( TimestampException $e ) {
+                       return false;
+               }
        }
  
        public function setBigSelects( $value = true ) {
                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)." );
                }
        }
  }