Merge "Add action/user tracking to html cache purge jobs"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 9 Nov 2017 22:33:48 +0000 (22:33 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 9 Nov 2017 22:33:48 +0000 (22:33 +0000)
1  2 
includes/Title.php
includes/filerepo/file/LocalFile.php
includes/page/PageArchive.php
includes/page/WikiPage.php

diff --combined includes/Title.php
@@@ -3628,20 -3628,19 +3628,20 @@@ class Title implements LinkTarget 
                $blNamespace = "{$prefix}_namespace";
                $blTitle = "{$prefix}_title";
  
 +              $pageQuery = WikiPage::getQueryInfo();
                $res = $db->select(
 -                      [ $table, 'page' ],
 +                      [ $table, 'nestpage' => $pageQuery['tables'] ],
                        array_merge(
                                [ $blNamespace, $blTitle ],
 -                              WikiPage::selectFields()
 +                              $pageQuery['fields']
                        ),
                        [ "{$prefix}_from" => $id ],
                        __METHOD__,
                        $options,
 -                      [ 'page' => [
 +                      [ 'nestpage' => [
                                'LEFT JOIN',
                                [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
 -                      ] ]
 +                      ] ] + $pageQuery['joins']
                );
  
                $retVal = [];
                $pageId = $this->getArticleID( $flags );
                if ( $pageId ) {
                        $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
 -                      $row = $db->selectRow( 'revision', Revision::selectFields(),
 +                      $revQuery = Revision::getQueryInfo();
 +                      $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
                                [ 'rev_page' => $pageId ],
                                __METHOD__,
                                [
                                        'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
 -                                      'IGNORE INDEX' => 'rev_timestamp', // See T159319
 -                              ]
 +                                      'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
 +                              ],
 +                              $revQuery['joins']
                        );
                        if ( $row ) {
                                return new Revision( $row );
         * on the number of links. Typically called on create and delete.
         */
        public function touchLinks() {
-               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
+               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
                if ( $this->getNamespace() == NS_CATEGORY ) {
-                       DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
+                       );
                }
        }
  
@@@ -183,10 -183,7 +183,10 @@@ class LocalFile extends File 
                        $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
                }
  
 -              $row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
 +              $fileQuery = self::getQueryInfo();
 +              $row = $dbr->selectRow(
 +                      $fileQuery['tables'], $fileQuery['fields'], $conds, __METHOD__, [], $fileQuery['joins']
 +              );
                if ( $row ) {
                        return self::newFromRow( $row, $repo );
                } else {
  
        /**
         * Fields in the image table
 -       * @todo Deprecate this in favor of a method that returns tables and joins
 -       *  as well, and use CommentStore::getJoin().
 +       * @deprecated since 1.31, use self::getQueryInfo() instead.
         * @return array
         */
        static function selectFields() {
 +              wfDeprecated( __METHOD__, '1.31' );
                return [
                        'img_name',
                        'img_size',
                ] + CommentStore::newKey( 'img_description' )->getFields();
        }
  
 +      /**
 +       * Return the tables, fields, and join conditions to be selected to create
 +       * a new localfile object.
 +       * @since 1.31
 +       * @param string[] $options
 +       *   - omit-lazy: Omit fields that are lazily cached.
 +       * @return array With three keys:
 +       *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
 +       *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
 +       *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
 +       */
 +      public static function getQueryInfo( array $options = [] ) {
 +              $commentQuery = CommentStore::newKey( 'img_description' )->getJoin();
 +              $ret = [
 +                      'tables' => [ 'image' ] + $commentQuery['tables'],
 +                      'fields' => [
 +                              'img_name',
 +                              'img_size',
 +                              'img_width',
 +                              'img_height',
 +                              'img_metadata',
 +                              'img_bits',
 +                              'img_media_type',
 +                              'img_major_mime',
 +                              'img_minor_mime',
 +                              'img_user',
 +                              'img_user_text',
 +                              'img_timestamp',
 +                              'img_sha1',
 +                      ] + $commentQuery['fields'],
 +                      'joins' => $commentQuery['joins'],
 +              ];
 +
 +              if ( in_array( 'omit-nonlazy', $options, true ) ) {
 +                      // Internal use only for getting only the lazy fields
 +                      $ret['fields'] = [];
 +              }
 +              if ( !in_array( 'omit-lazy', $options, true ) ) {
 +                      // Note: Keep this in sync with self::getLazyCacheFields()
 +                      $ret['fields'][] = 'img_metadata';
 +              }
 +
 +              return $ret;
 +      }
 +
        /**
         * Do not call this except from inside a repo class.
         * @param Title $title
        }
  
        /**
 -       * @param string $prefix
 +       * Returns the list of object properties that are included as-is in the cache.
 +       * @param string $prefix Must be the empty string
         * @return array
 +       * @since 1.31 No longer accepts a non-empty $prefix
         */
 -      function getCacheFields( $prefix = 'img_' ) {
 -              static $fields = [ 'size', 'width', 'height', 'bits', 'media_type',
 -                      'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
 -                      'user_text' ];
 -              static $results = [];
 -
 -              if ( $prefix == '' ) {
 -                      return array_merge( $fields, [ 'description' ] );
 -              }
 -              if ( !isset( $results[$prefix] ) ) {
 -                      $prefixedFields = [];
 -                      foreach ( $fields as $field ) {
 -                              $prefixedFields[] = $prefix . $field;
 -                      }
 -                      $prefixedFields += CommentStore::newKey( "{$prefix}description" )->getFields();
 -                      $results[$prefix] = $prefixedFields;
 +      protected function getCacheFields( $prefix = 'img_' ) {
 +              if ( $prefix !== '' ) {
 +                      throw new InvalidArgumentException(
 +                              __METHOD__ . ' with a non-empty prefix is no longer supported.'
 +                      );
                }
  
 -              return $results[$prefix];
 +              // See self::getQueryInfo() for the fetching of the data from the DB,
 +              // self::loadFromRow() for the loading of the object from the DB row,
 +              // and self::loadFromCache() for the caching, and self::setProps() for
 +              // populating the object from an array of data.
 +              return [ 'size', 'width', 'height', 'bits', 'media_type',
 +                      'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
 +                      'user_text', 'description' ];
        }
  
        /**
 -       * @param string $prefix
 +       * Returns the list of object properties that are included as-is in the
 +       * cache, only when they're not too big, and are lazily loaded by self::loadExtraFromDB().
 +       * @param string $prefix Must be the empty string
         * @return array
 +       * @since 1.31 No longer accepts a non-empty $prefix
         */
 -      function getLazyCacheFields( $prefix = 'img_' ) {
 -              static $fields = [ 'metadata' ];
 -              static $results = [];
 -
 -              if ( $prefix == '' ) {
 -                      return $fields;
 -              }
 -
 -              if ( !isset( $results[$prefix] ) ) {
 -                      $prefixedFields = [];
 -                      foreach ( $fields as $field ) {
 -                              $prefixedFields[] = $prefix . $field;
 -                      }
 -                      $results[$prefix] = $prefixedFields;
 +      protected function getLazyCacheFields( $prefix = 'img_' ) {
 +              if ( $prefix !== '' ) {
 +                      throw new InvalidArgumentException(
 +                              __METHOD__ . ' with a non-empty prefix is no longer supported.'
 +                      );
                }
  
 -              return $results[$prefix];
 +              // Keep this in sync with the omit-lazy option in self::getQueryInfo().
 +              return [ 'metadata' ];
        }
  
        /**
                        ? $this->repo->getMasterDB()
                        : $this->repo->getReplicaDB();
  
 -              $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
 -                      [ 'img_name' => $this->getName() ], $fname );
 +              $fileQuery = static::getQueryInfo();
 +              $row = $dbr->selectRow(
 +                      $fileQuery['tables'],
 +                      $fileQuery['fields'],
 +                      [ 'img_name' => $this->getName() ],
 +                      $fname,
 +                      [],
 +                      $fileQuery['joins']
 +              );
  
                if ( $row ) {
                        $this->loadFromRow( $row );
                # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
                $this->extraDataLoaded = true;
  
 -              $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
 +              $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getReplicaDB(), $fname );
                if ( !$fieldMap ) {
 -                      $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
 +                      $fieldMap = $this->loadExtraFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
                }
  
                if ( $fieldMap ) {
         * @param string $fname
         * @return array|bool
         */
 -      private function loadFieldsWithTimestamp( $dbr, $fname ) {
 +      private function loadExtraFieldsWithTimestamp( $dbr, $fname ) {
                $fieldMap = false;
  
 -              $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), [
 +              $fileQuery = self::getQueryInfo( [ 'omit-nonlazy' ] );
 +              $row = $dbr->selectRow(
 +                      $fileQuery['tables'],
 +                      $fileQuery['fields'],
 +                      [
                                'img_name' => $this->getName(),
 -                              'img_timestamp' => $dbr->timestamp( $this->getTimestamp() )
 -                      ], $fname );
 +                              'img_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
 +                      ],
 +                      $fname,
 +                      [],
 +                      $fileQuery['joins']
 +              );
                if ( $row ) {
                        $fieldMap = $this->unprefixRow( $row, 'img_' );
                } else {
                        # File may have been uploaded over in the meantime; check the old versions
 -                      $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), [
 +                      $fileQuery = OldLocalFile::getQueryInfo( [ 'omit-nonlazy' ] );
 +                      $row = $dbr->selectRow(
 +                              $fileQuery['tables'],
 +                              $fileQuery['fields'],
 +                              [
                                        'oi_name' => $this->getName(),
 -                                      'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() )
 -                              ], $fname );
 +                                      'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
 +                              ],
 +                              $fname,
 +                              [],
 +                              $fileQuery['joins']
 +                      );
                        if ( $row ) {
                                $fieldMap = $this->unprefixRow( $row, 'oi_' );
                        }
                }
  
 +              if ( isset( $fieldMap['metadata'] ) ) {
 +                      $fieldMap['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $fieldMap['metadata'] );
 +              }
 +
                return $fieldMap;
        }
  
        function decodeRow( $row, $prefix = 'img_' ) {
                $decoded = $this->unprefixRow( $row, $prefix );
  
 +              $decoded['description'] = CommentStore::newKey( 'description' )
 +                      ->getComment( (object)$decoded )->text;
 +
                $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
  
                $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
                $this->dataLoaded = true;
                $this->extraDataLoaded = true;
  
 -              $this->description = CommentStore::newKey( "{$prefix}description" )
 -                      // $row is probably using getFields() from self::getCacheFields()
 -                      ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row )->text;
 -
                $array = $this->decodeRow( $row, $prefix );
  
                foreach ( $array as $name => $value ) {
         */
        function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
                $dbr = $this->repo->getReplicaDB();
 -              $tables = [ 'oldimage' ];
 -              $fields = OldLocalFile::selectFields();
 -              $conds = $opts = $join_conds = [];
 +              $oldFileQuery = OldLocalFile::getQueryInfo();
 +
 +              $tables = $oldFileQuery['tables'];
 +              $fields = $oldFileQuery['fields'];
 +              $join_conds = $oldFileQuery['joins'];
 +              $conds = $opts = [];
                $eq = $inc ? '=' : '';
                $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
  
                $dbr = $this->repo->getReplicaDB();
  
                if ( $this->historyLine == 0 ) { // called for the first time, return line from cur
 -                      $this->historyRes = $dbr->select( 'image',
 -                              self::selectFields() + [
 +                      $fileQuery = self::getQueryInfo();
 +                      $this->historyRes = $dbr->select( $fileQuery['tables'],
 +                              $fileQuery['fields'] + [
                                        'oi_archive_name' => $dbr->addQuotes( '' ),
                                        'oi_deleted' => 0,
                                ],
                                [ 'img_name' => $this->title->getDBkey() ],
 -                              $fname
 +                              $fname,
 +                              [],
 +                              $fileQuery['joins']
                        );
  
                        if ( 0 == $dbr->numRows( $this->historyRes ) ) {
                                return false;
                        }
                } elseif ( $this->historyLine == 1 ) {
 +                      $fileQuery = OldLocalFile::getQueryInfo();
                        $this->historyRes = $dbr->select(
 -                              'oldimage',
 -                              OldLocalFile::selectFields(),
 +                              $fileQuery['tables'],
 +                              $fileQuery['fields'],
                                [ 'oi_name' => $this->title->getDBkey() ],
                                $fname,
 -                              [ 'ORDER BY' => 'oi_timestamp DESC' ]
 +                              [ 'ORDER BY' => 'oi_timestamp DESC' ],
 +                              $fileQuery['joins']
                        );
                }
                $this->historyLine++;
                }
  
                # Invalidate cache for all pages using this file
-               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ) );
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
+               );
  
                return Status::newGood();
        }
@@@ -2492,23 -2420,22 +2494,23 @@@ class LocalFileDeleteBatch 
                }
  
                if ( count( $oldRels ) ) {
 +                      $fileQuery = OldLocalFile::getQueryInfo();
                        $res = $dbw->select(
 -                              'oldimage',
 -                              OldLocalFile::selectFields(),
 +                              $fileQuery['tables'],
 +                              $fileQuery['fields'],
                                [
                                        'oi_name' => $this->file->getName(),
                                        'oi_archive_name' => array_keys( $oldRels )
                                ],
                                __METHOD__,
 -                              [ 'FOR UPDATE' ]
 +                              [ 'FOR UPDATE' ],
 +                              $fileQuery['joins']
                        );
                        $rowsInsert = [];
                        if ( $res->numRows() ) {
                                $reason = $commentStoreFaReason->createComment( $dbw, $this->reason );
                                foreach ( $res as $row ) {
 -                                      // Legacy from OldLocalFile::selectFields() just above
 -                                      $comment = $commentStoreOiDesc->getCommentLegacy( $dbw, $row );
 +                                      $comment = $commentStoreOiDesc->getComment( $row );
                                        $rowsInsert[] = [
                                                // Deletion-specific fields
                                                'fa_storage_group' => 'deleted',
@@@ -2755,14 -2682,12 +2757,14 @@@ class LocalFileRestoreBatch 
                        $conditions['fa_id'] = $this->ids;
                }
  
 +              $arFileQuery = ArchivedFile::getQueryInfo();
                $result = $dbw->select(
 -                      'filearchive',
 -                      ArchivedFile::selectFields(),
 +                      $arFileQuery['tables'],
 +                      $arFileQuery['fields'],
                        $conditions,
                        __METHOD__,
 -                      [ 'ORDER BY' => 'fa_timestamp DESC' ]
 +                      [ 'ORDER BY' => 'fa_timestamp DESC' ],
 +                      $arFileQuery['joins']
                );
  
                $idsPresent = [];
                                ];
                        }
  
 -                      // Legacy from ArchivedFile::selectFields() just above
 -                      $comment = $commentStoreFaDesc->getCommentLegacy( $dbw, $row );
 +                      $comment = $commentStoreFaDesc->getComment( $row );
                        if ( $first && !$exists ) {
                                // This revision will be published as the new current version
                                $destRel = $this->file->getRel();
@@@ -231,14 -231,12 +231,14 @@@ class PageArchive 
                }
  
                $dbr = wfGetDB( DB_REPLICA );
 +              $fileQuery = ArchivedFile::getQueryInfo();
                return $dbr->select(
 -                      'filearchive',
 -                      ArchivedFile::selectFields(),
 +                      $fileQuery['tables'],
 +                      $fileQuery['fields'],
                        [ 'fa_name' => $this->title->getDBkey() ],
                        __METHOD__,
 -                      [ 'ORDER BY' => 'fa_timestamp DESC' ]
 +                      [ 'ORDER BY' => 'fa_timestamp DESC' ],
 +                      $fileQuery['joins']
                );
        }
  
         */
        public function getRevision( $timestamp ) {
                $dbr = wfGetDB( DB_REPLICA );
 -              $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
 -
 -              $tables = [ 'archive' ] + $commentQuery['tables'];
 -
 -              $fields = [
 -                      'ar_rev_id',
 -                      'ar_text',
 -                      'ar_user',
 -                      'ar_user_text',
 -                      'ar_timestamp',
 -                      'ar_minor_edit',
 -                      'ar_flags',
 -                      'ar_text_id',
 -                      'ar_deleted',
 -                      'ar_len',
 -                      'ar_sha1',
 -              ] + $commentQuery['fields'];
 -
 -              if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
 -                      $fields[] = 'ar_content_format';
 -                      $fields[] = 'ar_content_model';
 -              }
 -
 -              $join_conds = [] + $commentQuery['joins'];
 +              $arQuery = Revision::getArchiveQueryInfo();
  
                $row = $dbr->selectRow(
 -                      $tables,
 -                      $fields,
 +                      $arQuery['tables'],
 +                      $arQuery['fields'],
                        [
                                'ar_namespace' => $this->title->getNamespace(),
                                'ar_title' => $this->title->getDBkey(),
                        ],
                        __METHOD__,
                        [],
 -                      $join_conds
 +                      $arQuery['joins']
                );
  
                if ( $row ) {
                        Hooks::run( 'ArticleUndelete',
                                [ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
                        if ( $this->title->getNamespace() == NS_FILE ) {
-                               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->title, 'imagelinks' ) );
+                               DeferredUpdates::addUpdate(
+                                       new HTMLCacheUpdate( $this->title, 'imagelinks', 'file-restore' )
+                               );
                        }
                }
  
@@@ -158,11 -158,8 +158,11 @@@ class WikiPage implements Page, IDBAcce
  
                $from = self::convertSelectType( $from );
                $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_REPLICA );
 +              $pageQuery = self::getQueryInfo();
                $row = $db->selectRow(
 -                      'page', self::selectFields(), [ 'page_id' => $id ], __METHOD__ );
 +                      $pageQuery['tables'], $pageQuery['fields'], [ 'page_id' => $id ], __METHOD__,
 +                      [], $pageQuery['joins']
 +              );
                if ( !$row ) {
                        return null;
                }
         * Return the list of revision fields that should be selected to create
         * a new page.
         *
 +       * @deprecated since 1.31, use self::getQueryInfo() instead.
         * @return array
         */
        public static function selectFields() {
                global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
  
 +              wfDeprecated( __METHOD__, '1.31' );
 +
                $fields = [
                        'page_id',
                        'page_namespace',
                return $fields;
        }
  
 +      /**
 +       * Return the tables, fields, and join conditions to be selected to create
 +       * a new page object.
 +       * @since 1.31
 +       * @return array With three keys:
 +       *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
 +       *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
 +       *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
 +       */
 +      public static function getQueryInfo() {
 +              global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
 +
 +              $ret = [
 +                      'tables' => [ 'page' ],
 +                      'fields' => [
 +                              'page_id',
 +                              'page_namespace',
 +                              'page_title',
 +                              'page_restrictions',
 +                              'page_is_redirect',
 +                              'page_is_new',
 +                              'page_random',
 +                              'page_touched',
 +                              'page_links_updated',
 +                              'page_latest',
 +                              'page_len',
 +                      ],
 +                      'joins' => [],
 +              ];
 +
 +              if ( $wgContentHandlerUseDB ) {
 +                      $ret['fields'][] = 'page_content_model';
 +              }
 +
 +              if ( $wgPageLanguageUseDB ) {
 +                      $ret['fields'][] = 'page_lang';
 +              }
 +
 +              return $ret;
 +      }
 +
        /**
         * Fetch a page record with the given conditions
         * @param IDatabase $dbr
         * @return object|bool Database result resource, or false on failure
         */
        protected function pageData( $dbr, $conditions, $options = [] ) {
 -              $fields = self::selectFields();
 +              $pageQuery = self::getQueryInfo();
  
                // Avoid PHP 7.1 warning of passing $this by reference
                $wikiPage = $this;
  
 -              Hooks::run( 'ArticlePageDataBefore', [ &$wikiPage, &$fields ] );
 +              Hooks::run( 'ArticlePageDataBefore', [
 +                      &$wikiPage, &$pageQuery['fields'], &$pageQuery['tables'], &$pageQuery['joins']
 +              ] );
  
 -              $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
 +              $row = $dbr->selectRow(
 +                      $pageQuery['tables'],
 +                      $pageQuery['fields'],
 +                      $conditions,
 +                      __METHOD__,
 +                      $options,
 +                      $pageQuery['joins']
 +              );
  
                Hooks::run( 'ArticlePageDataAfter', [ &$wikiPage, &$row ] );
  
                        throw new MWException( "Could not find text for current revision {$oldid}." );
                }
  
 -              // @TODO: pass content object?!
 -              $revision = new Revision( [
 -                      'page'       => $this->getId(),
 -                      'title'      => $this->mTitle, // for determining the default content model
 -                      'comment'    => $summary,
 -                      'minor_edit' => $meta['minor'],
 -                      'text'       => $meta['serialized'],
 -                      'len'        => $newsize,
 -                      'parent_id'  => $oldid,
 -                      'user'       => $user->getId(),
 -                      'user_text'  => $user->getName(),
 -                      'timestamp'  => $now,
 -                      'content_model' => $content->getModel(),
 -                      'content_format' => $meta['serialFormat'],
 -              ] );
 -
                $changed = !$content->equals( $oldContent );
  
                $dbw = wfGetDB( DB_MASTER );
  
                if ( $changed ) {
 +                      // @TODO: pass content object?!
 +                      $revision = new Revision( [
 +                              'page'       => $this->getId(),
 +                              'title'      => $this->mTitle, // for determining the default content model
 +                              'comment'    => $summary,
 +                              'minor_edit' => $meta['minor'],
 +                              'text'       => $meta['serialized'],
 +                              'len'        => $newsize,
 +                              'parent_id'  => $oldid,
 +                              'user'       => $user->getId(),
 +                              'user_text'  => $user->getName(),
 +                              'timestamp'  => $now,
 +                              'content_model' => $content->getModel(),
 +                              'content_format' => $meta['serialFormat'],
 +                      ] );
 +
                        $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
                        $status->merge( $prepStatus );
                        if ( !$status->isOK() ) {
                } else {
                        // T34948: revision ID must be set to page {{REVISIONID}} and
                        // related variables correctly. Likewise for {{REVISIONUSER}} (T135261).
 -                      $revision->setId( $this->getLatest() );
 -                      $revision->setUserIdAndName(
 -                              $this->getUser( Revision::RAW ),
 -                              $this->getUserText( Revision::RAW )
 -                      );
 +                      // Since we don't insert a new revision into the database, the least
 +                      // error-prone way is to reuse given old revision.
 +                      $revision = $meta['oldRevision'];
                }
  
                if ( $changed ) {
                $revCommentStore = new CommentStore( 'rev_comment' );
                $arCommentStore = new CommentStore( 'ar_comment' );
  
 -              $fields = Revision::selectFields();
 +              $revQuery = Revision::getQueryInfo();
                $bitfield = false;
  
                // Bitfields to further suppress the content
                if ( $suppress ) {
                        $bitfield = Revision::SUPPRESSED_ALL;
 -                      $fields = array_diff( $fields, [ 'rev_deleted' ] );
 +                      $revQuery['fields'] = array_diff( $revQuery['fields'], [ 'rev_deleted' ] );
                }
  
                // For now, shunt the revision data into the archive table.
                // the rev_deleted field, which is reserved for this purpose.
  
                // Get all of the page revisions
 -              $commentQuery = $revCommentStore->getJoin();
                $res = $dbw->select(
 -                      [ 'revision' ] + $commentQuery['tables'],
 -                      $fields + $commentQuery['fields'],
 +                      $revQuery['tables'],
 +                      $revQuery['fields'],
                        [ 'rev_page' => $id ],
                        __METHOD__,
                        'FOR UPDATE',
 -                      $commentQuery['joins']
 +                      $revQuery['joins']
                );
  
                // Build their equivalent archive rows
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
  
                // Invalidate caches of articles which include this page
-               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'templatelinks' ) );
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
+               );
  
                if ( $title->getNamespace() == NS_CATEGORY ) {
                        // Load the Category object, which will schedule a job to create
  
                // Images
                if ( $title->getNamespace() == NS_FILE ) {
-                       DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
+                       DeferredUpdates::addUpdate(
+                               new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
+                       );
                }
  
                // User talk pages
         */
        public static function onArticleEdit( Title $title, Revision $revision = null ) {
                // Invalidate caches of articles which include this page
-               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'templatelinks' ) );
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
+               );
  
                // Invalidate the caches of all pages which redirect here
-               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'redirect' ) );
+               DeferredUpdates::addUpdate(
+                       new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
+               );
  
                MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );