Hello Bene, Cicalese, Ladsgroup, Brion VIBBER, Legoktm, Addshore, jenkins-bot, 
Anomie,

I'd like you to do a code review.  Please visit

    https://gerrit.wikimedia.org/r/399156

to review the following change.


Change subject: Revert "[MCR] Turn Revision into a proxy to new code."
......................................................................

Revert "[MCR] Turn Revision into a proxy to new code."

This reverts commit 9dcc56b3c9e7a3212d0900ee23808b9402cad952.

With this patch applied, newly created revisions are sometimes not fund just 
after submitting an edit, until replicas have caught up. Our best theory is 
that it somehow interfere with ChronologyProtector, but we don't have a good 
idea how.

Also, as legoktm mentioned, the commit message is terrible and needs fixing.

Change-Id: Idf3404f3fa8f8d08a7fb2ab8268726e2c1edecfe
---
M RELEASE-NOTES-1.31
M docs/hooks.txt
M includes/Revision.php
M includes/ServiceWiring.php
M includes/actions/HistoryAction.php
M includes/cache/MessageCache.php
M includes/page/WikiPage.php
M includes/parser/Parser.php
M includes/resourceloader/ResourceLoaderWikiModule.php
M includes/specials/SpecialNewpages.php
M tests/phpunit/includes/MediaWikiServicesTest.php
M tests/phpunit/includes/RevisionDbTestBase.php
M tests/phpunit/includes/RevisionTest.php
13 files changed, 1,414 insertions(+), 752 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/56/399156/1

diff --git a/RELEASE-NOTES-1.31 b/RELEASE-NOTES-1.31
index 67026f4..1a1a9f7 100644
--- a/RELEASE-NOTES-1.31
+++ b/RELEASE-NOTES-1.31
@@ -123,9 +123,6 @@
 * The Block class will no longer accept usable-but-missing usernames for
   'byText' or ->setBlocker(). Callers should either ensure the blocker exists
   locally or use a new interwiki-format username like "iw>Example".
-* The RevisionInsertComplete hook is now deprecated, use 
RevisionRecordInserted instead.
-  RevisionInsertComplete is still called, but the second and third parameter 
will always be null.
-  Hard deprecation is scheduled for 1.32.
 * The following methods that get and set ParserOutput state are deprecated.
   Callers should use the new stateless $options parameter to
   ParserOutput::getText() instead.
diff --git a/docs/hooks.txt b/docs/hooks.txt
index 1f4a5f4..ee38ea9 100644
--- a/docs/hooks.txt
+++ b/docs/hooks.txt
@@ -2810,14 +2810,14 @@
   added to any module.
 &$ResourceLoader: object
 
-'RevisionRecordInserted': Called after a revision is inserted into the 
database.
-$revisionRecord: the RevisionRecord that has just been inserted.
-
-'RevisionInsertComplete': DEPRECATED! Use RevisionRecordInserted hook instead.
-Called after a revision is inserted into the database.
-$revision: the Revision
-$data: DEPRECATED! Always null!
-$flags: DEPRECATED! Always null!
+'RevisionInsertComplete': Called after a revision is inserted into the 
database.
+&$revision: the Revision
+$data: the data stored in old_text.  The meaning depends on $flags: if external
+  is set, it's the URL of the revision text in external storage; otherwise,
+  it's the revision text itself.  In either case, if gzip is set, the revision
+  text is gzipped.
+$flags: a comma-delimited list of strings representing the options used.  May
+  include: utf8 (this will always be set for new revisions); gzip; external.
 
 'SearchableNamespaces': An option to modify which namespaces are searchable.
 &$arr: Array of namespaces ($nsId => $name) which will be used.
diff --git a/includes/Revision.php b/includes/Revision.php
index ea73a61..25c89c2 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -20,14 +20,7 @@
  * @file
  */
 
-use MediaWiki\Storage\MutableRevisionRecord;
-use MediaWiki\Storage\RevisionAccessException;
-use MediaWiki\Storage\RevisionRecord;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreRecord;
-use MediaWiki\Storage\SlotRecord;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWiki\User\UserIdentityValue;
+use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
@@ -35,50 +28,78 @@
 use Wikimedia\Rdbms\FakeResultWrapper;
 
 /**
- * @deprecated since 1.31, use RevisionRecord, RevisionStore, and BlobStore 
instead.
+ * @todo document
  */
 class Revision implements IDBAccessObject {
+       /** @var int|null */
+       protected $mId;
+       /** @var int|null */
+       protected $mPage;
+       /** @var string */
+       protected $mUserText;
+       /** @var string */
+       protected $mOrigUserText;
+       /** @var int */
+       protected $mUser;
+       /** @var bool */
+       protected $mMinorEdit;
+       /** @var string */
+       protected $mTimestamp;
+       /** @var int */
+       protected $mDeleted;
+       /** @var int */
+       protected $mSize;
+       /** @var string */
+       protected $mSha1;
+       /** @var int */
+       protected $mParentId;
+       /** @var string */
+       protected $mComment;
+       /** @var string */
+       protected $mText;
+       /** @var int */
+       protected $mTextId;
+       /** @var int */
+       protected $mUnpatrolled;
 
-       /** @var RevisionRecord */
-       protected $mRecord;
+       /** @var stdClass|null */
+       protected $mTextRow;
+
+       /**  @var null|Title */
+       protected $mTitle;
+       /** @var bool */
+       protected $mCurrent;
+       /** @var string */
+       protected $mContentModel;
+       /** @var string */
+       protected $mContentFormat;
+
+       /** @var Content|null|bool */
+       protected $mContent;
+       /** @var null|ContentHandler */
+       protected $mContentHandler;
+
+       /** @var int */
+       protected $mQueryFlags = 0;
+       /** @var bool Used for cached values to reload user text and 
rev_deleted */
+       protected $mRefreshMutableFields = false;
+       /** @var string Wiki ID; false means the current wiki */
+       protected $mWiki = false;
 
        // Revision deletion constants
-       const DELETED_TEXT = RevisionRecord::DELETED_TEXT;
-       const DELETED_COMMENT = RevisionRecord::DELETED_COMMENT;
-       const DELETED_USER = RevisionRecord::DELETED_USER;
-       const DELETED_RESTRICTED = RevisionRecord::DELETED_RESTRICTED;
-       const SUPPRESSED_USER = RevisionRecord::SUPPRESSED_USER;
-       const SUPPRESSED_ALL = RevisionRecord::SUPPRESSED_ALL;
+       const DELETED_TEXT = 1;
+       const DELETED_COMMENT = 2;
+       const DELETED_USER = 4;
+       const DELETED_RESTRICTED = 8;
+       const SUPPRESSED_USER = 12; // convenience
+       const SUPPRESSED_ALL = 15; // convenience
 
        // Audience options for accessors
-       const FOR_PUBLIC = RevisionRecord::FOR_PUBLIC;
-       const FOR_THIS_USER = RevisionRecord::FOR_THIS_USER;
-       const RAW = RevisionRecord::RAW;
+       const FOR_PUBLIC = 1;
+       const FOR_THIS_USER = 2;
+       const RAW = 3;
 
-       const TEXT_CACHE_GROUP = SqlBlobStore::TEXT_CACHE_GROUP;
-
-       /**
-        * @return RevisionStore
-        */
-       protected static function getRevisionStore() {
-               return MediaWikiServices::getInstance()->getRevisionStore();
-       }
-
-       /**
-        * @return SqlBlobStore
-        */
-       protected static function getBlobStore() {
-               $store = MediaWikiServices::getInstance()->getBlobStore();
-
-               if ( !$store instanceof SqlBlobStore ) {
-                       throw new RuntimeException(
-                               'The backwards compatibility code in Revision 
currently requires the BlobStore '
-                               . 'service to be an SqlBlobStore instance, but 
it is a ' . get_class( $store )
-                       );
-               }
-
-               return $store;
-       }
+       const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and 
max key count
 
        /**
         * Load a page revision from a given revision ID number.
@@ -93,8 +114,7 @@
         * @return Revision|null
         */
        public static function newFromId( $id, $flags = 0 ) {
-               $rec = self::getRevisionStore()->getRevisionById( $id, $flags );
-               return $rec === null ? null : new Revision( $rec, $flags );
+               return self::newFromConds( [ 'rev_id' => intval( $id ) ], 
$flags );
        }
 
        /**
@@ -112,8 +132,20 @@
         * @return Revision|null
         */
        public static function newFromTitle( LinkTarget $linkTarget, $id = 0, 
$flags = 0 ) {
-               $rec = self::getRevisionStore()->getRevisionByTitle( 
$linkTarget, $id, $flags );
-               return $rec === null ? null : new Revision( $rec, $flags );
+               $conds = [
+                       'page_namespace' => $linkTarget->getNamespace(),
+                       'page_title' => $linkTarget->getDBkey()
+               ];
+               if ( $id ) {
+                       // Use the specified ID
+                       $conds['rev_id'] = $id;
+                       return self::newFromConds( $conds, $flags );
+               } else {
+                       // Use a join to get the latest revision
+                       $conds[] = 'rev_id=page_latest';
+                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? 
DB_MASTER : DB_REPLICA );
+                       return self::loadFromConds( $db, $conds, $flags );
+               }
        }
 
        /**
@@ -131,13 +163,22 @@
         * @return Revision|null
         */
        public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) 
{
-               $rec = self::getRevisionStore()->getRevisionByPageId( $pageId, 
$revId, $flags );
-               return $rec === null ? null : new Revision( $rec, $flags );
+               $conds = [ 'page_id' => $pageId ];
+               if ( $revId ) {
+                       $conds['rev_id'] = $revId;
+                       return self::newFromConds( $conds, $flags );
+               } else {
+                       // Use a join to get the latest revision
+                       $conds[] = 'rev_id = page_latest';
+                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? 
DB_MASTER : DB_REPLICA );
+                       return self::loadFromConds( $db, $conds, $flags );
+               }
        }
 
        /**
         * Make a fake revision object from an archive table row. This is 
queried
         * for permissions or even inserted (as in Special:Undelete)
+        * @todo FIXME: Should be a subclass for RevisionDelete. [TS]
         *
         * @param object $row
         * @param array $overrides
@@ -146,53 +187,74 @@
         * @return Revision
         */
        public static function newFromArchiveRow( $row, $overrides = [] ) {
-               $rec = self::getRevisionStore()->newRevisionFromArchiveRow( 
$row, 0, null, $overrides );
-               return new Revision( $rec );
+               global $wgContentHandlerUseDB;
+
+               $attribs = $overrides + [
+                       'page'       => isset( $row->ar_page_id ) ? 
$row->ar_page_id : null,
+                       'id'         => isset( $row->ar_rev_id ) ? 
$row->ar_rev_id : null,
+                       'comment'    => CommentStore::newKey( 'ar_comment' )
+                               // Legacy because $row may have come from 
self::selectArchiveFields()
+                               ->getCommentLegacy( wfGetDB( DB_REPLICA ), 
$row, true )->text,
+                       'user'       => $row->ar_user,
+                       'user_text'  => $row->ar_user_text,
+                       'timestamp'  => $row->ar_timestamp,
+                       'minor_edit' => $row->ar_minor_edit,
+                       'text_id'    => isset( $row->ar_text_id ) ? 
$row->ar_text_id : null,
+                       'deleted'    => $row->ar_deleted,
+                       'len'        => $row->ar_len,
+                       'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 
: null,
+                       'content_model'   => isset( $row->ar_content_model ) ? 
$row->ar_content_model : null,
+                       'content_format'  => isset( $row->ar_content_format ) ? 
$row->ar_content_format : null,
+               ];
+
+               if ( !$wgContentHandlerUseDB ) {
+                       unset( $attribs['content_model'] );
+                       unset( $attribs['content_format'] );
+               }
+
+               if ( !isset( $attribs['title'] )
+                       && isset( $row->ar_namespace )
+                       && isset( $row->ar_title )
+               ) {
+                       $attribs['title'] = Title::makeTitle( 
$row->ar_namespace, $row->ar_title );
+               }
+
+               if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
+                       // Pre-1.5 ar_text row
+                       $attribs['text'] = self::getRevisionText( $row, 'ar_' );
+                       if ( $attribs['text'] === false ) {
+                               throw new MWException( 'Unable to load text 
from archive row (possibly T24624)' );
+                       }
+               }
+               return new self( $attribs );
        }
 
        /**
         * @since 1.19
         *
-        * MCR migration note: replaced by RevisionStore::newRevisionFromRow(). 
Note that
-        * newFromRow() also accepts arrays, while newRevisionFromRow() does 
not. Instead,
-        * a MutableRevisionRecord should be constructed directly. 
RevisionStore::newRevisionFromArray()
-        * can be used as a temporary replacement, but should be avoided.
-        *
-        * @param object|array $row
+        * @param object $row
         * @return Revision
         */
        public static function newFromRow( $row ) {
-               if ( is_array( $row ) ) {
-                       $rec = 
self::getRevisionStore()->newMutableRevisionFromArray( $row );
-               } else {
-                       $rec = self::getRevisionStore()->newRevisionFromRow( 
$row );
-               }
-
-               return new Revision( $rec );
+               return new self( $row );
        }
 
        /**
         * Load a page revision from a given revision ID number.
         * Returns null if no such revision can be found.
         *
-        * @deprecated since 1.31, use RevisionStore::getRevisionById() instead.
-        *
         * @param IDatabase $db
         * @param int $id
         * @return Revision|null
         */
        public static function loadFromId( $db, $id ) {
-               wfDeprecated( __METHOD__, '1.31' ); // no known callers
-               $rec = self::getRevisionStore()->loadRevisionFromId( $db, $id );
-               return $rec === null ? null : new Revision( $rec );
+               return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] 
);
        }
 
        /**
         * Load either the current, or a specified, revision
         * that's attached to a given page. If not attached
         * to that page, will return null.
-        *
-        * @deprecated since 1.31, use RevisionStore::getRevisionByPageId() 
instead.
         *
         * @param IDatabase $db
         * @param int $pageid
@@ -200,8 +262,13 @@
         * @return Revision|null
         */
        public static function loadFromPageId( $db, $pageid, $id = 0 ) {
-               $rec = self::getRevisionStore()->loadRevisionFromPageId( $db, 
$pageid, $id );
-               return $rec === null ? null : new Revision( $rec );
+               $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => 
intval( $pageid ) ];
+               if ( $id ) {
+                       $conds['rev_id'] = intval( $id );
+               } else {
+                       $conds[] = 'rev_id=page_latest';
+               }
+               return self::loadFromConds( $db, $conds );
        }
 
        /**
@@ -209,16 +276,24 @@
         * that's attached to a given page. If not attached
         * to that page, will return null.
         *
-        * @deprecated since 1.31, use RevisionStore::getRevisionByTitle() 
instead.
-        *
         * @param IDatabase $db
         * @param Title $title
         * @param int $id
         * @return Revision|null
         */
        public static function loadFromTitle( $db, $title, $id = 0 ) {
-               $rec = self::getRevisionStore()->loadRevisionFromTitle( $db, 
$title, $id );
-               return $rec === null ? null : new Revision( $rec );
+               if ( $id ) {
+                       $matchId = intval( $id );
+               } else {
+                       $matchId = 'page_latest';
+               }
+               return self::loadFromConds( $db,
+                       [
+                               "rev_id=$matchId",
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey()
+                       ]
+               );
        }
 
        /**
@@ -226,17 +301,73 @@
         * WARNING: Timestamps may in some circumstances not be unique,
         * so this isn't the best key to use.
         *
-        * @deprecated since 1.31, use 
RevisionStore::loadRevisionFromTimestamp() instead.
-        *
         * @param IDatabase $db
         * @param Title $title
         * @param string $timestamp
         * @return Revision|null
         */
        public static function loadFromTimestamp( $db, $title, $timestamp ) {
-               // XXX: replace loadRevisionFromTimestamp by 
getRevisionByTimestamp?
-               $rec = self::getRevisionStore()->loadRevisionFromTimestamp( 
$db, $title, $timestamp );
-               return $rec === null ? null : new Revision( $rec );
+               return self::loadFromConds( $db,
+                       [
+                               'rev_timestamp' => $db->timestamp( $timestamp ),
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey()
+                       ]
+               );
+       }
+
+       /**
+        * Given a set of conditions, fetch a revision
+        *
+        * This method is used then a revision ID is qualified and
+        * will incorporate some basic replica DB/master fallback logic
+        *
+        * @param array $conditions
+        * @param int $flags (optional)
+        * @return Revision|null
+        */
+       private static function newFromConds( $conditions, $flags = 0 ) {
+               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : 
DB_REPLICA );
+
+               $rev = self::loadFromConds( $db, $conditions, $flags );
+               // Make sure new pending/committed revision are visibile later 
on
+               // within web requests to certain avoid bugs like T93866 and 
T94407.
+               if ( !$rev
+                       && !( $flags & self::READ_LATEST )
+                       && wfGetLB()->getServerCount() > 1
+                       && wfGetLB()->hasOrMadeRecentMasterChanges()
+               ) {
+                       $flags = self::READ_LATEST;
+                       $db = wfGetDB( DB_MASTER );
+                       $rev = self::loadFromConds( $db, $conditions, $flags );
+               }
+
+               if ( $rev ) {
+                       $rev->mQueryFlags = $flags;
+               }
+
+               return $rev;
+       }
+
+       /**
+        * Given a set of conditions, fetch a revision from
+        * the given database connection.
+        *
+        * @param IDatabase $db
+        * @param array $conditions
+        * @param int $flags (optional)
+        * @return Revision|null
+        */
+       private static function loadFromConds( $db, $conditions, $flags = 0 ) {
+               $row = self::fetchFromConds( $db, $conditions, $flags );
+               if ( $row ) {
+                       $rev = new Revision( $row );
+                       $rev->mWiki = $db->getDomainID();
+
+                       return $rev;
+               }
+
+               return null;
        }
 
        /**
@@ -246,18 +377,52 @@
         *
         * @param LinkTarget $title
         * @return ResultWrapper
-        * @deprecated Since 1.28, no callers in core nor in known extensions. 
No-op since 1.31.
+        * @deprecated Since 1.28
         */
        public static function fetchRevision( LinkTarget $title ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               return new FakeResultWrapper( [] );
+               $row = self::fetchFromConds(
+                       wfGetDB( DB_REPLICA ),
+                       [
+                               'rev_id=page_latest',
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey()
+                       ]
+               );
+
+               return new FakeResultWrapper( $row ? [ $row ] : [] );
+       }
+
+       /**
+        * Given a set of conditions, return a ResultWrapper
+        * which will return matching database rows with the
+        * fields necessary to build Revision objects.
+        *
+        * @param IDatabase $db
+        * @param array $conditions
+        * @param int $flags (optional)
+        * @return stdClass
+        */
+       private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
+               $revQuery = self::getQueryInfo( [ 'page', 'user' ] );
+               $options = [];
+               if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
+                       $options[] = 'FOR UPDATE';
+               }
+               return $db->selectRow(
+                       $revQuery['tables'],
+                       $revQuery['fields'],
+                       $conditions,
+                       __METHOD__,
+                       $options,
+                       $revQuery['joins']
+               );
        }
 
        /**
         * Return the value of a select() JOIN conds array for the user table.
         * This will get user table rows for logged-in users.
         * @since 1.19
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] 
) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead.
         * @return array
         */
        public static function userJoinCond() {
@@ -269,7 +434,7 @@
         * Return the value of a select() page conds array for the page table.
         * This will assure that the revision(s) are not orphaned from live 
pages.
         * @since 1.19
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] 
) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead.
         * @return array
         */
        public static function pageJoinCond() {
@@ -280,7 +445,7 @@
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision.
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
+        * @deprecated since 1.31, use self::getQueryInfo() instead.
         * @return array
         */
        public static function selectFields() {
@@ -315,7 +480,7 @@
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision from an archive row.
-        * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() 
instead.
+        * @deprecated since 1.31, use self::getArchiveQueryInfo() instead.
         * @return array
         */
        public static function selectArchiveFields() {
@@ -351,7 +516,7 @@
        /**
         * Return the list of text fields that should be selected to read the
         * revision text
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'text' ] 
) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'text' ] ) instead.
         * @return array
         */
        public static function selectTextFields() {
@@ -364,7 +529,7 @@
 
        /**
         * Return the list of page fields that should be selected from page 
table
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] 
) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead.
         * @return array
         */
        public static function selectPageFields() {
@@ -381,7 +546,7 @@
 
        /**
         * Return the list of user fields that should be selected from user 
table
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] 
) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead.
         * @return array
         */
        public static function selectUserFields() {
@@ -393,7 +558,6 @@
         * Return the tables, fields, and join conditions to be selected to 
create
         * a new revision object.
         * @since 1.31
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
         * @param array $options Any combination of the following strings
         *  - 'page': Join with the page table, and select fields to identify 
the page
         *  - 'user': Join with the user table, and select the user name
@@ -404,21 +568,104 @@
         *   - joins: (array) to include in the `$join_conds` to 
`IDatabase->select()`
         */
        public static function getQueryInfo( $options = [] ) {
-               return self::getRevisionStore()->getQueryInfo( $options );
+               global $wgContentHandlerUseDB;
+
+               $commentQuery = CommentStore::newKey( 'rev_comment' 
)->getJoin();
+               $ret = [
+                       'tables' => [ 'revision' ] + $commentQuery['tables'],
+                       'fields' => [
+                               'rev_id',
+                               'rev_page',
+                               'rev_text_id',
+                               'rev_timestamp',
+                               'rev_user_text',
+                               'rev_user',
+                               'rev_minor_edit',
+                               'rev_deleted',
+                               'rev_len',
+                               'rev_parent_id',
+                               'rev_sha1',
+                       ] + $commentQuery['fields'],
+                       'joins' => $commentQuery['joins'],
+               ];
+
+               if ( $wgContentHandlerUseDB ) {
+                       $ret['fields'][] = 'rev_content_format';
+                       $ret['fields'][] = 'rev_content_model';
+               }
+
+               if ( in_array( 'page', $options, true ) ) {
+                       $ret['tables'][] = 'page';
+                       $ret['fields'] = array_merge( $ret['fields'], [
+                               'page_namespace',
+                               'page_title',
+                               'page_id',
+                               'page_latest',
+                               'page_is_redirect',
+                               'page_len',
+                       ] );
+                       $ret['joins']['page'] = [ 'INNER JOIN', [ 'page_id = 
rev_page' ] ];
+               }
+
+               if ( in_array( 'user', $options, true ) ) {
+                       $ret['tables'][] = 'user';
+                       $ret['fields'] = array_merge( $ret['fields'], [
+                               'user_name',
+                       ] );
+                       $ret['joins']['user'] = [ 'LEFT JOIN', [ 'rev_user != 
0', 'user_id = rev_user' ] ];
+               }
+
+               if ( in_array( 'text', $options, true ) ) {
+                       $ret['tables'][] = 'text';
+                       $ret['fields'] = array_merge( $ret['fields'], [
+                               'old_text',
+                               'old_flags'
+                       ] );
+                       $ret['joins']['text'] = [ 'INNER JOIN', [ 
'rev_text_id=old_id' ] ];
+               }
+
+               return $ret;
        }
 
        /**
         * Return the tables, fields, and join conditions to be selected to 
create
         * a new archived revision object.
         * @since 1.31
-        * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() 
instead.
         * @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 getArchiveQueryInfo() {
-               return self::getRevisionStore()->getArchiveQueryInfo();
+               global $wgContentHandlerUseDB;
+
+               $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
+               $ret = [
+                       'tables' => [ 'archive' ] + $commentQuery['tables'],
+                       'fields' => [
+                               'ar_id',
+                               'ar_page_id',
+                               'ar_rev_id',
+                               'ar_text',
+                               'ar_text_id',
+                               'ar_timestamp',
+                               'ar_user_text',
+                               'ar_user',
+                               'ar_minor_edit',
+                               'ar_deleted',
+                               'ar_len',
+                               'ar_parent_id',
+                               'ar_sha1',
+                       ] + $commentQuery['fields'],
+                       'joins' => $commentQuery['joins'],
+               ];
+
+               if ( $wgContentHandlerUseDB ) {
+                       $ret['fields'][] = 'ar_content_format';
+                       $ret['fields'][] = 'ar_content_model';
+               }
+
+               return $ret;
        }
 
        /**
@@ -428,49 +675,203 @@
         * @return array
         */
        public static function getParentLengths( $db, array $revIds ) {
-               return self::getRevisionStore()->listRevisionSizes( $db, 
$revIds );
+               $revLens = [];
+               if ( !$revIds ) {
+                       return $revLens; // empty
+               }
+               $res = $db->select( 'revision',
+                       [ 'rev_id', 'rev_len' ],
+                       [ 'rev_id' => $revIds ],
+                       __METHOD__ );
+               foreach ( $res as $row ) {
+                       $revLens[$row->rev_id] = $row->rev_len;
+               }
+               return $revLens;
        }
 
        /**
-        * @param object|array|RevisionRecord $row Either a database row or an 
array
-        * @param int $queryFlags
-        * @param Title|null $title
-        *
+        * @param object|array $row Either a database row or an array
+        * @throws MWException
         * @access private
         */
-       function __construct( $row, $queryFlags = 0, Title $title = null ) {
-               global $wgUser;
-
-               if ( $row instanceof RevisionRecord ) {
-                       $this->mRecord = $row;
+       public function __construct( $row ) {
+               if ( is_object( $row ) ) {
+                       $this->constructFromDbRowObject( $row );
                } elseif ( is_array( $row ) ) {
-                       if ( !isset( $row['user'] ) && !isset( 
$row['user_text'] ) ) {
-                               $row['user'] = $wgUser;
-                       }
-
-                       $this->mRecord = 
self::getRevisionStore()->newMutableRevisionFromArray(
-                               $row,
-                               $queryFlags,
-                               $title
-                       );
-               } elseif ( is_object( $row ) ) {
-                       $this->mRecord = 
self::getRevisionStore()->newRevisionFromRow(
-                               $row,
-                               $queryFlags,
-                               $title
-                       );
+                       $this->constructFromRowArray( $row );
                } else {
-                       throw new InvalidArgumentException(
-                               '$row must be a row object, an associative 
array, or a RevisionRecord'
-                       );
+                       throw new MWException( 'Revision constructor passed 
invalid row format.' );
                }
+               $this->mUnpatrolled = null;
        }
 
        /**
-        * @return RevisionRecord
+        * @param object $row
         */
-       public function getRevisionRecord() {
-               return $this->mRecord;
+       private function constructFromDbRowObject( $row ) {
+               $this->mId = intval( $row->rev_id );
+               $this->mPage = intval( $row->rev_page );
+               $this->mTextId = intval( $row->rev_text_id );
+               $this->mComment = CommentStore::newKey( 'rev_comment' )
+                       // Legacy because $row may have come from 
self::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true 
)->text;
+               $this->mUser = intval( $row->rev_user );
+               $this->mMinorEdit = intval( $row->rev_minor_edit );
+               $this->mTimestamp = $row->rev_timestamp;
+               $this->mDeleted = intval( $row->rev_deleted );
+
+               if ( !isset( $row->rev_parent_id ) ) {
+                       $this->mParentId = null;
+               } else {
+                       $this->mParentId = intval( $row->rev_parent_id );
+               }
+
+               if ( !isset( $row->rev_len ) ) {
+                       $this->mSize = null;
+               } else {
+                       $this->mSize = intval( $row->rev_len );
+               }
+
+               if ( !isset( $row->rev_sha1 ) ) {
+                       $this->mSha1 = null;
+               } else {
+                       $this->mSha1 = $row->rev_sha1;
+               }
+
+               if ( isset( $row->page_latest ) ) {
+                       $this->mCurrent = ( $row->rev_id == $row->page_latest );
+                       $this->mTitle = Title::newFromRow( $row );
+               } else {
+                       $this->mCurrent = false;
+                       $this->mTitle = null;
+               }
+
+               if ( !isset( $row->rev_content_model ) ) {
+                       $this->mContentModel = null; # determine on demand if 
needed
+               } else {
+                       $this->mContentModel = strval( $row->rev_content_model 
);
+               }
+
+               if ( !isset( $row->rev_content_format ) ) {
+                       $this->mContentFormat = null; # determine on demand if 
needed
+               } else {
+                       $this->mContentFormat = strval( 
$row->rev_content_format );
+               }
+
+               // Lazy extraction...
+               $this->mText = null;
+               if ( isset( $row->old_text ) ) {
+                       $this->mTextRow = $row;
+               } else {
+                       // 'text' table row entry will be lazy-loaded
+                       $this->mTextRow = null;
+               }
+
+               // Use user_name for users and rev_user_text for IPs...
+               $this->mUserText = null; // lazy load if left null
+               if ( $this->mUser == 0 ) {
+                       $this->mUserText = $row->rev_user_text; // IP user
+               } elseif ( isset( $row->user_name ) ) {
+                       $this->mUserText = $row->user_name; // logged-in user
+               }
+               $this->mOrigUserText = $row->rev_user_text;
+       }
+
+       /**
+        * @param array $row
+        *
+        * @throws MWException
+        */
+       private function constructFromRowArray( array $row ) {
+               // Build a new revision to be saved...
+               global $wgUser; // ugh
+
+               # if we have a content object, use it to set the model and type
+               if ( !empty( $row['content'] ) ) {
+                       if ( !( $row['content'] instanceof Content ) ) {
+                               throw new MWException( '`content` field must 
contain a Content object.' );
+                       }
+
+                       // @todo when is that set? test with external store 
setup! check out insertOn() [dk]
+                       if ( !empty( $row['text_id'] ) ) {
+                               throw new MWException( "Text already stored in 
external store (id {$row['text_id']}), " .
+                                       "can't serialize content object" );
+                       }
+
+                       $row['content_model'] = $row['content']->getModel();
+                       # note: mContentFormat is initializes later accordingly
+                       # note: content is serialized later in this method!
+                       # also set text to null?
+               }
+
+               $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
+               $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : 
null;
+               $this->mTextId = isset( $row['text_id'] ) ? intval( 
$row['text_id'] ) : null;
+               $this->mUserText = isset( $row['user_text'] )
+                       ? strval( $row['user_text'] ) : $wgUser->getName();
+               $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : 
$wgUser->getId();
+               $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( 
$row['minor_edit'] ) : 0;
+               $this->mTimestamp = isset( $row['timestamp'] )
+                       ? strval( $row['timestamp'] ) : wfTimestampNow();
+               $this->mDeleted = isset( $row['deleted'] ) ? intval( 
$row['deleted'] ) : 0;
+               $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : 
null;
+               $this->mParentId = isset( $row['parent_id'] ) ? intval( 
$row['parent_id'] ) : null;
+               $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : 
null;
+
+               $this->mContentModel = isset( $row['content_model'] )
+                       ? strval( $row['content_model'] ) : null;
+               $this->mContentFormat = isset( $row['content_format'] )
+                       ? strval( $row['content_format'] ) : null;
+
+               // Enforce spacing trimming on supplied text
+               $this->mComment = isset( $row['comment'] ) ? trim( strval( 
$row['comment'] ) ) : null;
+               $this->mText = isset( $row['text'] ) ? rtrim( strval( 
$row['text'] ) ) : null;
+               $this->mTextRow = null;
+
+               $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+
+               // if we have a Content object, override mText and mContentModel
+               if ( !empty( $row['content'] ) ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContent = $row['content'];
+
+                       $this->mContentModel = $this->mContent->getModel();
+                       $this->mContentHandler = null;
+
+                       $this->mText = $handler->serializeContent( 
$row['content'], $this->getContentFormat() );
+               } elseif ( $this->mText !== null ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContent = $handler->unserializeContent( 
$this->mText );
+               }
+
+               // If we have a Title object, make sure it is consistent with 
mPage.
+               if ( $this->mTitle && $this->mTitle->exists() ) {
+                       if ( $this->mPage === null ) {
+                               // if the page ID wasn't known, set it now
+                               $this->mPage = $this->mTitle->getArticleID();
+                       } elseif ( $this->mTitle->getArticleID() !== 
$this->mPage ) {
+                               // Got different page IDs. This may be legit 
(e.g. during undeletion),
+                               // but it seems worth mentioning it in the log.
+                               wfDebug( "Page ID " . $this->mPage . " 
mismatches the ID " .
+                                       $this->mTitle->getArticleID() . " 
provided by the Title object." );
+                       }
+               }
+
+               $this->mCurrent = false;
+
+               // If we still have no length, see it we have the text to 
figure it out
+               if ( !$this->mSize && $this->mContent !== null ) {
+                       $this->mSize = $this->mContent->getSize();
+               }
+
+               // Same for sha1
+               if ( $this->mSha1 === null ) {
+                       $this->mSha1 = $this->mText === null ? null : 
self::base36Sha1( $this->mText );
+               }
+
+               // force lazy init
+               $this->getContentModel();
+               $this->getContentFormat();
        }
 
        /**
@@ -479,27 +880,19 @@
         * @return int|null
         */
        public function getId() {
-               return $this->mRecord->getId();
+               return $this->mId;
        }
 
        /**
         * Set the revision ID
         *
-        * This should only be used for proposed revisions that turn out to be 
null edits.
-        *
-        * @note Only supported on Revisions that were constructed based on 
associative arrays,
-        *       since they are mutable.
+        * This should only be used for proposed revisions that turn out to be 
null edits
         *
         * @since 1.19
-        * @param int|string $id
-        * @throws MWException
+        * @param int $id
         */
        public function setId( $id ) {
-               if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                       $this->mRecord->setId( intval( $id ) );
-               } else {
-                       throw new MWException( __METHOD__ . ' is not supported 
on this instance' );
-               }
+               $this->mId = (int)$id;
        }
 
        /**
@@ -507,107 +900,106 @@
         *
         * This should only be used for proposed revisions that turn out to be 
null edits
         *
-        * @note Only supported on Revisions that were constructed based on 
associative arrays,
-        *       since they are mutable.
-        *
         * @since 1.28
         * @deprecated since 1.31, please reuse old Revision object
         * @param int $id User ID
         * @param string $name User name
-        * @throws MWException
         */
        public function setUserIdAndName( $id, $name ) {
-               if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                       $user = new UserIdentityValue( intval( $id ), $name );
-                       $this->mRecord->setUser( $user );
-               } else {
-                       throw new MWException( __METHOD__ . ' is not supported 
on this instance' );
-               }
+               $this->mUser = (int)$id;
+               $this->mUserText = $name;
+               $this->mOrigUserText = $name;
        }
 
        /**
-        * @return SlotRecord
-        */
-       private function getMainSlotRaw() {
-               return $this->mRecord->getSlot( 'main', RevisionRecord::RAW );
-       }
-
-       /**
-        * Get the ID of the row of the text table that contains the content of 
the
-        * revision's main slot, if that content is stored in the text table.
-        *
-        * If the content is stored elsewhere, this returns null.
-        *
-        * @deprecated since 1.31, use 
RevisionRecord()->getSlot()->getContentAddress() to
-        * get that actual address that can be used with BlobStore::getBlob(); 
or use
-        * RevisionRecord::hasSameContent() to check if two revisions have the 
same content.
+        * Get text row ID
         *
         * @return int|null
         */
        public function getTextId() {
-               $slot = $this->getMainSlotRaw();
-               return $slot->hasAddress()
-                       ? self::getBlobStore()->getTextIdFromAddress( 
$slot->getAddress() )
-                       : null;
+               return $this->mTextId;
        }
 
        /**
         * Get parent revision ID (the original previous page revision)
         *
-        * @return int|null The ID of the parent revision. 0 indicates that 
there is no
-        * parent revision. Null indicates that the parent revision is not 
known.
+        * @return int|null
         */
        public function getParentId() {
-               return $this->mRecord->getParentId();
+               return $this->mParentId;
        }
 
        /**
         * Returns the length of the text in this revision, or null if unknown.
         *
-        * @return int
+        * @return int|null
         */
        public function getSize() {
-               return $this->mRecord->getSize();
+               return $this->mSize;
        }
 
        /**
-        * Returns the base36 sha1 of the content in this revision, or null if 
unknown.
+        * Returns the base36 sha1 of the text in this revision, or null if 
unknown.
         *
-        * @return string
+        * @return string|null
         */
        public function getSha1() {
-               // XXX: we may want to drop all the hashing logic, it's not 
worth the overhead.
-               return $this->mRecord->getSha1();
+               return $this->mSha1;
        }
 
        /**
-        * Returns the title of the page associated with this entry.
-        * Since 1.31, this will never return null.
+        * Returns the title of the page associated with this entry or null.
         *
         * Will do a query, when title is not set and id is given.
         *
-        * @return Title
+        * @return Title|null
         */
        public function getTitle() {
-               $linkTarget = $this->mRecord->getPageAsLinkTarget();
-               return Title::newFromLinkTarget( $linkTarget );
+               if ( $this->mTitle !== null ) {
+                       return $this->mTitle;
+               }
+               // rev_id is defined as NOT NULL, but this revision may not yet 
have been inserted.
+               if ( $this->mId !== null ) {
+                       $dbr = wfGetLB( $this->mWiki )->getConnectionRef( 
DB_REPLICA, [], $this->mWiki );
+                       // @todo: Title::getSelectFields(), or 
Title::getQueryInfo(), or something like that
+                       $row = $dbr->selectRow(
+                               [ 'revision', 'page' ],
+                               [
+                                       'page_namespace',
+                                       'page_title',
+                                       'page_id',
+                                       'page_latest',
+                                       'page_is_redirect',
+                                       'page_len',
+                               ],
+                               [ 'rev_id' => $this->mId ],
+                               __METHOD__,
+                               [],
+                               [ 'page' => [ 'JOIN', 'page_id=rev_page' ] ]
+                       );
+                       if ( $row ) {
+                               // @TODO: better foreign title handling
+                               $this->mTitle = Title::newFromRow( $row );
+                       }
+               }
+
+               if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
+                       // Loading by ID is best, though not possible for 
foreign titles
+                       if ( !$this->mTitle && $this->mPage !== null && 
$this->mPage > 0 ) {
+                               $this->mTitle = Title::newFromID( $this->mPage 
);
+                       }
+               }
+
+               return $this->mTitle;
        }
 
        /**
         * Set the title of the revision
         *
-        * @deprecated: since 1.31, this is now a noop. Pass the Title to the 
constructor instead.
-        *
         * @param Title $title
         */
        public function setTitle( $title ) {
-               if ( !$title->equals( $this->getTitle() ) ) {
-                       throw new InvalidArgumentException(
-                               $title->getPrefixedText()
-                                       . ' is not the same as '
-                                       . 
$this->mRecord->getPageAsLinkTarget()->__toString()
-                       );
-               }
+               $this->mTitle = $title;
        }
 
        /**
@@ -616,7 +1008,7 @@
         * @return int|null
         */
        public function getPage() {
-               return $this->mRecord->getPageId();
+               return $this->mPage;
        }
 
        /**
@@ -633,14 +1025,13 @@
         * @return int
         */
        public function getUser( $audience = self::FOR_PUBLIC, User $user = 
null ) {
-               global $wgUser;
-
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( 
self::DELETED_USER ) ) {
+                       return 0;
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( 
self::DELETED_USER, $user ) ) {
+                       return 0;
+               } else {
+                       return $this->mUser;
                }
-
-               $user = $this->mRecord->getUser( $audience, $user );
-               return $user ? $user->getId() : 0;
        }
 
        /**
@@ -668,14 +1059,23 @@
         * @return string
         */
        public function getUserText( $audience = self::FOR_PUBLIC, User $user = 
null ) {
-               global $wgUser;
+               $this->loadMutableFields();
 
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( 
self::DELETED_USER ) ) {
+                       return '';
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( 
self::DELETED_USER, $user ) ) {
+                       return '';
+               } else {
+                       if ( $this->mUserText === null ) {
+                               $this->mUserText = User::whoIs( $this->mUser ); 
// load on demand
+                               if ( $this->mUserText === false ) {
+                                       # This shouldn't happen, but it can if 
the wiki was recovered
+                                       # via importing revs and there is no 
user table entry yet.
+                                       $this->mUserText = $this->mOrigUserText;
+                               }
+                       }
+                       return $this->mUserText;
                }
-
-               $user = $this->mRecord->getUser( $audience, $user );
-               return $user ? $user->getName() : '';
        }
 
        /**
@@ -703,14 +1103,13 @@
         * @return string
         */
        function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
-               global $wgUser;
-
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( 
self::DELETED_COMMENT ) ) {
+                       return '';
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( 
self::DELETED_COMMENT, $user ) ) {
+                       return '';
+               } else {
+                       return $this->mComment;
                }
-
-               $comment = $this->mRecord->getComment( $audience, $user );
-               return $comment === null ? null : $comment->text;
        }
 
        /**
@@ -728,14 +1127,23 @@
         * @return bool
         */
        public function isMinor() {
-               return $this->mRecord->isMinor();
+               return (bool)$this->mMinorEdit;
        }
 
        /**
         * @return int Rcid of the unpatrolled row, zero if there isn't one
         */
        public function isUnpatrolled() {
-               return self::getRevisionStore()->isUnpatrolled( $this->mRecord 
);
+               if ( $this->mUnpatrolled !== null ) {
+                       return $this->mUnpatrolled;
+               }
+               $rc = $this->getRecentChange();
+               if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
+                       $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
+               } else {
+                       $this->mUnpatrolled = 0;
+               }
+               return $this->mUnpatrolled;
        }
 
        /**
@@ -748,7 +1156,19 @@
         * @return RecentChange|null
         */
        public function getRecentChange( $flags = 0 ) {
-               return self::getRevisionStore()->getRecentChange( 
$this->mRecord, $flags );
+               $dbr = wfGetDB( DB_REPLICA );
+
+               list( $dbType, ) = DBAccessObjectUtils::getDBOptions( $flags );
+
+               return RecentChange::newFromConds(
+                       [
+                               'rc_user_text' => $this->getUserText( self::RAW 
),
+                               'rc_timestamp' => $dbr->timestamp( 
$this->getTimestamp() ),
+                               'rc_this_oldid' => $this->getId()
+                       ],
+                       __METHOD__,
+                       $dbType
+               );
        }
 
        /**
@@ -757,7 +1177,14 @@
         * @return bool
         */
        public function isDeleted( $field ) {
-               return $this->mRecord->isDeleted( $field );
+               if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
+                       // Current revisions of pages cannot have the content 
hidden. Skipping this
+                       // check is very useful for Parser as it fetches 
templates using newKnownCurrent().
+                       // Calling getVisibility() in that case triggers a 
verification database query.
+                       return false; // no need to check
+               }
+
+               return ( $this->getVisibility() & $field ) == $field;
        }
 
        /**
@@ -766,17 +1193,19 @@
         * @return int
         */
        public function getVisibility() {
-               return $this->mRecord->getVisibility();
+               $this->loadMutableFields();
+
+               return (int)$this->mDeleted;
        }
 
        /**
         * Fetch revision content if it's available to the specified audience.
         * If the specified audience does not have the ability to view this
-        * revision, or the content could not be loaded, null will be returned.
+        * revision, null will be returned.
         *
         * @param int $audience One of:
         *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to $user
+        *   Revision::FOR_THIS_USER    to be displayed to $wgUser
         *   Revision::RAW              get the text regardless of permissions
         * @param User $user User object to check for, only if FOR_THIS_USER is 
passed
         *   to the $audience parameter
@@ -784,17 +1213,12 @@
         * @return Content|null
         */
        public function getContent( $audience = self::FOR_PUBLIC, User $user = 
null ) {
-               global $wgUser;
-
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
-               }
-
-               try {
-                       return $this->mRecord->getContent( 'main', $audience, 
$user );
-               }
-               catch ( RevisionAccessException $e ) {
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( 
self::DELETED_TEXT ) ) {
                        return null;
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( 
self::DELETED_TEXT, $user ) ) {
+                       return null;
+               } else {
+                       return $this->getContentInternal();
                }
        }
 
@@ -802,51 +1226,86 @@
         * Get original serialized data (without checking view restrictions)
         *
         * @since 1.21
-        * @deprecated since 1.31, use BlobStore::getBlob instead.
-        *
         * @return string
         */
        public function getSerializedData() {
-               $slot = $this->getMainSlotRaw();
-               return $slot->getContent()->serialize();
+               if ( $this->mText === null ) {
+                       // Revision is immutable. Load on demand.
+                       $this->mText = $this->loadText();
+               }
+
+               return $this->mText;
        }
 
        /**
-        * Returns the content model for the main slot of this revision.
+        * Gets the content object for the revision (or null on failure).
+        *
+        * Note that for mutable Content objects, each call to this method will 
return a
+        * fresh clone.
+        *
+        * @since 1.21
+        * @return Content|null The Revision's content, or null on failure.
+        */
+       protected function getContentInternal() {
+               if ( $this->mContent === null ) {
+                       $text = $this->getSerializedData();
+
+                       if ( $text !== null && $text !== false ) {
+                               // Unserialize content
+                               $handler = $this->getContentHandler();
+                               $format = $this->getContentFormat();
+
+                               $this->mContent = $handler->unserializeContent( 
$text, $format );
+                       }
+               }
+
+               // NOTE: copy() will return $this for immutable content objects
+               return $this->mContent ? $this->mContent->copy() : null;
+       }
+
+       /**
+        * Returns the content model for this revision.
         *
         * If no content model was stored in the database, the default content 
model for the title is
         * used to determine the content model to use. If no title is know, 
CONTENT_MODEL_WIKITEXT
         * is used as a last resort.
         *
-        * @todo: drop this, with MCR, there no longer is a single model 
associated with a revision.
-        *
         * @return string The content model id associated with this revision,
         *     see the CONTENT_MODEL_XXX constants.
         */
        public function getContentModel() {
-               return $this->getMainSlotRaw()->getModel();
+               if ( !$this->mContentModel ) {
+                       $title = $this->getTitle();
+                       if ( $title ) {
+                               $this->mContentModel = 
ContentHandler::getDefaultModelFor( $title );
+                       } else {
+                               $this->mContentModel = CONTENT_MODEL_WIKITEXT;
+                       }
+
+                       assert( !empty( $this->mContentModel ) );
+               }
+
+               return $this->mContentModel;
        }
 
        /**
-        * Returns the content format for the main slot of this revision.
+        * Returns the content format for this revision.
         *
         * If no content format was stored in the database, the default format 
for this
         * revision's content model is returned.
-        *
-        * @todo: drop this, the format is irrelevant to the revision!
         *
         * @return string The content format id associated with this revision,
         *     see the CONTENT_FORMAT_XXX constants.
         */
        public function getContentFormat() {
-               $format = $this->getMainSlotRaw()->getFormat();
+               if ( !$this->mContentFormat ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContentFormat = $handler->getDefaultFormat();
 
-               if ( $format === null ) {
-                       // if no format was stored along with the blob, fall 
back to default format
-                       $format = 
$this->getContentHandler()->getDefaultFormat();
+                       assert( !empty( $this->mContentFormat ) );
                }
 
-               return $format;
+               return $this->mContentFormat;
        }
 
        /**
@@ -856,21 +1315,33 @@
         * @return ContentHandler
         */
        public function getContentHandler() {
-               return ContentHandler::getForModelID( $this->getContentModel() 
);
+               if ( !$this->mContentHandler ) {
+                       $model = $this->getContentModel();
+                       $this->mContentHandler = ContentHandler::getForModelID( 
$model );
+
+                       $format = $this->getContentFormat();
+
+                       if ( !$this->mContentHandler->isSupportedFormat( 
$format ) ) {
+                               throw new MWException( "Oops, the content 
format $format is not supported for "
+                                       . "this content model, $model" );
+                       }
+               }
+
+               return $this->mContentHandler;
        }
 
        /**
         * @return string
         */
        public function getTimestamp() {
-               return $this->mRecord->getTimestamp();
+               return wfTimestamp( TS_MW, $this->mTimestamp );
        }
 
        /**
         * @return bool
         */
        public function isCurrent() {
-               return ( $this->mRecord instanceof RevisionStoreRecord ) && 
$this->mRecord->isCurrent();
+               return $this->mCurrent;
        }
 
        /**
@@ -879,8 +1350,13 @@
         * @return Revision|null
         */
        public function getPrevious() {
-               $rec = self::getRevisionStore()->getPreviousRevision( 
$this->mRecord );
-               return $rec === null ? null : new Revision( $rec );
+               if ( $this->getTitle() ) {
+                       $prev = $this->getTitle()->getPreviousRevisionID( 
$this->getId() );
+                       if ( $prev ) {
+                               return self::newFromTitle( $this->getTitle(), 
$prev );
+                       }
+               }
+               return null;
        }
 
        /**
@@ -889,8 +1365,38 @@
         * @return Revision|null
         */
        public function getNext() {
-               $rec = self::getRevisionStore()->getNextRevision( 
$this->mRecord );
-               return $rec === null ? null : new Revision( $rec );
+               if ( $this->getTitle() ) {
+                       $next = $this->getTitle()->getNextRevisionID( 
$this->getId() );
+                       if ( $next ) {
+                               return self::newFromTitle( $this->getTitle(), 
$next );
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Get previous revision Id for this page_id
+        * This is used to populate rev_parent_id on save
+        *
+        * @param IDatabase $db
+        * @return int
+        */
+       private function getPreviousRevisionId( $db ) {
+               if ( $this->mPage === null ) {
+                       return 0;
+               }
+               # Use page_latest if ID is not given
+               if ( !$this->mId ) {
+                       $prevId = $db->selectField( 'page', 'page_latest',
+                               [ 'page_id' => $this->mPage ],
+                               __METHOD__ );
+               } else {
+                       $prevId = $db->selectField( 'revision', 'rev_id',
+                               [ 'rev_page' => $this->mPage, 'rev_id < ' . 
$this->mId ],
+                               __METHOD__,
+                               [ 'ORDER BY' => 'rev_id DESC' ] );
+               }
+               return intval( $prevId );
        }
 
        /**
@@ -923,9 +1429,35 @@
                        return false;
                }
 
-               $cacheKey = isset( $row->old_id ) ? ( 'tt:' . $row->old_id ) : 
null;
+               // Use external methods for external objects, text in table is 
URL-only then
+               if ( in_array( 'external', $flags ) ) {
+                       $url = $text;
+                       $parts = explode( '://', $url, 2 );
+                       if ( count( $parts ) == 1 || $parts[1] == '' ) {
+                               return false;
+                       }
 
-               return self::getBlobStore()->expandBlob( $text, $flags, 
$cacheKey );
+                       if ( isset( $row->old_id ) && $wiki === false ) {
+                               // Make use of the wiki-local revision text 
cache
+                               $cache = 
MediaWikiServices::getInstance()->getMainWANObjectCache();
+                               // The cached value should be decompressed, so 
handle that and return here
+                               return $cache->getWithSetCallback(
+                                       $cache->makeKey( 'revisiontext', 
'textid', $row->old_id ),
+                                       self::getCacheTTL( $cache ),
+                                       function () use ( $url, $wiki, $flags ) 
{
+                                               // No negative caching per 
Revision::loadText()
+                                               $text = 
ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
+
+                                               return 
self::decompressRevisionText( $text, $flags );
+                                       },
+                                       [ 'pcGroup' => self::TEXT_CACHE_GROUP, 
'pcTTL' => $cache::TTL_PROC_LONG ]
+                               );
+                       } else {
+                               $text = ExternalStore::fetchFromURL( $url, [ 
'wiki' => $wiki ] );
+                       }
+               }
+
+               return self::decompressRevisionText( $text, $flags );
        }
 
        /**
@@ -939,7 +1471,28 @@
         * @return string
         */
        public static function compressRevisionText( &$text ) {
-               return self::getBlobStore()->compressData( $text );
+               global $wgCompressRevisions;
+               $flags = [];
+
+               # Revisions not marked this way will be converted
+               # on load if $wgLegacyCharset is set in the future.
+               $flags[] = 'utf-8';
+
+               if ( $wgCompressRevisions ) {
+                       if ( function_exists( 'gzdeflate' ) ) {
+                               $deflated = gzdeflate( $text );
+
+                               if ( $deflated === false ) {
+                                       wfLogWarning( __METHOD__ . ': 
gzdeflate() failed' );
+                               } else {
+                                       $text = $deflated;
+                                       $flags[] = 'gzip';
+                               }
+                       } else {
+                               wfDebug( __METHOD__ . " -- no zlib support, not 
compressing\n" );
+                       }
+               }
+               return implode( ',', $flags );
        }
 
        /**
@@ -950,7 +1503,46 @@
         * @return string|bool Decompressed text, or false on failure
         */
        public static function decompressRevisionText( $text, $flags ) {
-               return self::getBlobStore()->decompressData( $text, $flags );
+               global $wgLegacyEncoding, $wgContLang;
+
+               if ( $text === false ) {
+                       // Text failed to be fetched; nothing to do
+                       return false;
+               }
+
+               if ( in_array( 'gzip', $flags ) ) {
+                       # Deal with optional compression of archived pages.
+                       # This can be done periodically via 
maintenance/compressOld.php, and
+                       # as pages are saved if $wgCompressRevisions is set.
+                       $text = gzinflate( $text );
+
+                       if ( $text === false ) {
+                               wfLogWarning( __METHOD__ . ': gzinflate() 
failed' );
+                               return false;
+                       }
+               }
+
+               if ( in_array( 'object', $flags ) ) {
+                       # Generic compressed storage
+                       $obj = unserialize( $text );
+                       if ( !is_object( $obj ) ) {
+                               // Invalid object
+                               return false;
+                       }
+                       $text = $obj->getText();
+               }
+
+               if ( $text !== false && $wgLegacyEncoding
+                       && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', 
$flags )
+               ) {
+                       # Old revisions kept around in a legacy encoding?
+                       # Upconvert on demand.
+                       # ("utf8" checked for compatibility with some broken
+                       #  conversion scripts 2008-12-30)
+                       $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', 
$text );
+               }
+
+               return $text;
        }
 
        /**
@@ -962,27 +1554,192 @@
         * @return int The revision ID
         */
        public function insertOn( $dbw ) {
-               global $wgUser;
+               global $wgDefaultExternalStore, $wgContentHandlerUseDB;
 
-               // Note that $this->mRecord->getId() will typically return null 
here, but not always,
-               // e.g. not when restoring a revision.
+               // We're inserting a new revision, so we have to use master 
anyway.
+               // If it's a null revision, it may have references to rows that
+               // are not in the replica yet (the text row).
+               $this->mQueryFlags |= self::READ_LATEST;
 
-               if ( $this->mRecord->getUser( RevisionRecord::RAW ) === null ) {
-                       if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                               $this->mRecord->setUser( $wgUser );
+               // Not allowed to have rev_page equal to 0, false, etc.
+               if ( !$this->mPage ) {
+                       $title = $this->getTitle();
+                       if ( $title instanceof Title ) {
+                               $titleText = ' for page ' . 
$title->getPrefixedText();
                        } else {
-                               throw new MWException( 'Cannot insert revision 
with no associated user.' );
+                               $titleText = '';
+                       }
+                       throw new MWException( "Cannot insert 
revision$titleText: page ID must be nonzero" );
+               }
+
+               $this->checkContentModel();
+
+               $data = $this->mText;
+               $flags = self::compressRevisionText( $data );
+
+               # Write to external storage if required
+               if ( $wgDefaultExternalStore ) {
+                       // Store and get the URL
+                       $data = ExternalStore::insertToDefault( $data );
+                       if ( !$data ) {
+                               throw new MWException( "Unable to store text to 
external storage" );
+                       }
+                       if ( $flags ) {
+                               $flags .= ',';
+                       }
+                       $flags .= 'external';
+               }
+
+               # Record the text (or external storage URL) to the text table
+               if ( $this->mTextId === null ) {
+                       $dbw->insert( 'text',
+                               [
+                                       'old_text' => $data,
+                                       'old_flags' => $flags,
+                               ], __METHOD__
+                       );
+                       $this->mTextId = $dbw->insertId();
+               }
+
+               if ( $this->mComment === null ) {
+                       $this->mComment = "";
+               }
+
+               # Record the edit in revisions
+               $row = [
+                       'rev_page'       => $this->mPage,
+                       'rev_text_id'    => $this->mTextId,
+                       'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+                       'rev_user'       => $this->mUser,
+                       'rev_user_text'  => $this->mUserText,
+                       'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp 
),
+                       'rev_deleted'    => $this->mDeleted,
+                       'rev_len'        => $this->mSize,
+                       'rev_parent_id'  => $this->mParentId === null
+                               ? $this->getPreviousRevisionId( $dbw )
+                               : $this->mParentId,
+                       'rev_sha1'       => $this->mSha1 === null
+                               ? self::base36Sha1( $this->mText )
+                               : $this->mSha1,
+               ];
+               if ( $this->mId !== null ) {
+                       $row['rev_id'] = $this->mId;
+               }
+
+               list( $commentFields, $commentCallback ) =
+                       CommentStore::newKey( 'rev_comment' 
)->insertWithTempTable( $dbw, $this->mComment );
+               $row += $commentFields;
+
+               if ( $wgContentHandlerUseDB ) {
+                       // NOTE: Store null for the default model and format, 
to save space.
+                       // XXX: Makes the DB sensitive to changed defaults.
+                       // Make this behavior optional? Only in miser mode?
+
+                       $model = $this->getContentModel();
+                       $format = $this->getContentFormat();
+
+                       $title = $this->getTitle();
+
+                       if ( $title === null ) {
+                               throw new MWException( "Insufficient 
information to determine the title of the "
+                                       . "revision's page!" );
+                       }
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( 
$title );
+                       $defaultFormat = ContentHandler::getForModelID( 
$defaultModel )->getDefaultFormat();
+
+                       $row['rev_content_model'] = ( $model === $defaultModel 
) ? null : $model;
+                       $row['rev_content_format'] = ( $format === 
$defaultFormat ) ? null : $format;
+               }
+
+               $dbw->insert( 'revision', $row, __METHOD__ );
+
+               if ( $this->mId === null ) {
+                       // Only if auto-increment was used
+                       $this->mId = $dbw->insertId();
+               }
+               $commentCallback( $this->mId );
+
+               // Assertion to try to catch T92046
+               if ( (int)$this->mId === 0 ) {
+                       throw new UnexpectedValueException(
+                               'After insert, Revision mId is ' . var_export( 
$this->mId, 1 ) . ': ' .
+                                       var_export( $row, 1 )
+                       );
+               }
+
+               // Insert IP revision into ip_changes for use when querying for 
a range.
+               if ( $this->mUser === 0 && IP::isValid( $this->mUserText ) ) {
+                       $ipcRow = [
+                               'ipc_rev_id'        => $this->mId,
+                               'ipc_rev_timestamp' => $row['rev_timestamp'],
+                               'ipc_hex'           => IP::toHex( 
$row['rev_user_text'] ),
+                       ];
+                       $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
+               }
+
+               // Avoid PHP 7.1 warning of passing $this by reference
+               $revision = $this;
+               Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, 
$flags ] );
+
+               return $this->mId;
+       }
+
+       protected function checkContentModel() {
+               global $wgContentHandlerUseDB;
+
+               // Note: may return null for revisions that have not yet been 
inserted
+               $title = $this->getTitle();
+
+               $model = $this->getContentModel();
+               $format = $this->getContentFormat();
+               $handler = $this->getContentHandler();
+
+               if ( !$handler->isSupportedFormat( $format ) ) {
+                       $t = $title->getPrefixedDBkey();
+
+                       throw new MWException( "Can't use format $format with 
content model $model on $t" );
+               }
+
+               if ( !$wgContentHandlerUseDB && $title ) {
+                       // if $wgContentHandlerUseDB is not set,
+                       // all revisions must use the default content model and 
format.
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( 
$title );
+                       $defaultHandler = ContentHandler::getForModelID( 
$defaultModel );
+                       $defaultFormat = $defaultHandler->getDefaultFormat();
+
+                       if ( $this->getContentModel() != $defaultModel ) {
+                               $t = $title->getPrefixedDBkey();
+
+                               throw new MWException( "Can't save non-default 
content model with "
+                                       . "\$wgContentHandlerUseDB disabled: 
model is $model, "
+                                       . "default for $t is $defaultModel" );
+                       }
+
+                       if ( $this->getContentFormat() != $defaultFormat ) {
+                               $t = $title->getPrefixedDBkey();
+
+                               throw new MWException( "Can't use non-default 
content format with "
+                                       . "\$wgContentHandlerUseDB disabled: 
format is $format, "
+                                       . "default for $t is $defaultFormat" );
                        }
                }
 
-               $rec = self::getRevisionStore()->insertRevisionOn( 
$this->mRecord, $dbw );
+               $content = $this->getContent( self::RAW );
+               $prefixedDBkey = $title->getPrefixedDBkey();
+               $revId = $this->mId;
 
-               $this->mRecord = $rec;
-
-               // TODO: hard-deprecate in 1.32 (or even 1.31?)
-               Hooks::run( 'RevisionInsertComplete', [ $this, null, null ] );
-
-               return $rec->getId();
+               if ( !$content ) {
+                       throw new MWException(
+                               "Content of revision $revId ($prefixedDBkey) 
could not be loaded for validation!"
+                       );
+               }
+               if ( !$content->isValid() ) {
+                       throw new MWException(
+                               "Content of revision $revId ($prefixedDBkey) is 
not valid! Content model is $model"
+                       );
+               }
        }
 
        /**
@@ -991,7 +1748,103 @@
         * @return string
         */
        public static function base36Sha1( $text ) {
-               return SlotRecord::base36Sha1( $text );
+               return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
+       }
+
+       /**
+        * Get the text cache TTL
+        *
+        * @param WANObjectCache $cache
+        * @return int
+        */
+       private static function getCacheTTL( WANObjectCache $cache ) {
+               global $wgRevisionCacheExpiry;
+
+               if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= 
$cache::QOS_EMULATION_SQL ) {
+                       // Do not cache RDBMs blobs in...the RDBMs store
+                       $ttl = $cache::TTL_UNCACHEABLE;
+               } else {
+                       $ttl = $wgRevisionCacheExpiry ?: 
$cache::TTL_UNCACHEABLE;
+               }
+
+               return $ttl;
+       }
+
+       /**
+        * Lazy-load the revision's text.
+        * Currently hardcoded to the 'text' table storage engine.
+        *
+        * @return string|bool The revision's text, or false on failure
+        */
+       private function loadText() {
+               $cache = ObjectCache::getMainWANInstance();
+
+               // No negative caching; negative hits on text rows may be due 
to corrupted replica DBs
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'revisiontext', 'textid', 
$this->getTextId() ),
+                       self::getCacheTTL( $cache ),
+                       function () {
+                               return $this->fetchText();
+                       },
+                       [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => 
$cache::TTL_PROC_LONG ]
+               );
+       }
+
+       private function fetchText() {
+               $textId = $this->getTextId();
+
+               // If we kept data for lazy extraction, use it now...
+               if ( $this->mTextRow !== null ) {
+                       $row = $this->mTextRow;
+                       $this->mTextRow = null;
+               } else {
+                       $row = null;
+               }
+
+               // Callers doing updates will pass in READ_LATEST as usual. 
Since the text/blob tables
+               // do not normally get rows changed around, set 
READ_LATEST_IMMUTABLE in those cases.
+               $flags = $this->mQueryFlags;
+               $flags |= DBAccessObjectUtils::hasFlags( $flags, 
self::READ_LATEST )
+                       ? self::READ_LATEST_IMMUTABLE
+                       : 0;
+
+               list( $index, $options, $fallbackIndex, $fallbackOptions ) =
+                       DBAccessObjectUtils::getDBOptions( $flags );
+
+               if ( !$row ) {
+                       // Text data is immutable; check replica DBs first.
+                       $row = wfGetDB( $index )->selectRow(
+                               'text',
+                               [ 'old_text', 'old_flags' ],
+                               [ 'old_id' => $textId ],
+                               __METHOD__,
+                               $options
+                       );
+               }
+
+               // Fallback to DB_MASTER in some cases if the row was not found
+               if ( !$row && $fallbackIndex !== null ) {
+                       // Use FOR UPDATE if it was used to fetch this 
revision. This avoids missing the row
+                       // due to REPEATABLE-READ. Also fallback to the master 
if READ_LATEST is provided.
+                       $row = wfGetDB( $fallbackIndex )->selectRow(
+                               'text',
+                               [ 'old_text', 'old_flags' ],
+                               [ 'old_id' => $textId ],
+                               __METHOD__,
+                               $fallbackOptions
+                       );
+               }
+
+               if ( !$row ) {
+                       wfDebugLog( 'Revision', "No text row with ID '$textId' 
(revision {$this->getId()})." );
+               }
+
+               $text = self::getRevisionText( $row );
+               if ( $row && $text === false ) {
+                       wfDebugLog( 'Revision', "No blob for text row '$textId' 
(revision {$this->getId()})." );
+               }
+
+               return is_string( $text ) ? $text : false;
        }
 
        /**
@@ -1010,17 +1863,58 @@
         * @return Revision|null Revision or null on error
         */
        public static function newNullRevision( $dbw, $pageId, $summary, 
$minor, $user = null ) {
-               global $wgUser;
-               if ( !$user ) {
-                       $user = $wgUser;
+               global $wgContentHandlerUseDB;
+
+               $fields = [ 'page_latest', 'page_namespace', 'page_title',
+                                               'rev_text_id', 'rev_len', 
'rev_sha1' ];
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'rev_content_model';
+                       $fields[] = 'rev_content_format';
                }
 
-               $comment = CommentStoreComment::newUnsavedComment( $summary, 
null );
+               $current = $dbw->selectRow(
+                       [ 'page', 'revision' ],
+                       $fields,
+                       [
+                               'page_id' => $pageId,
+                               'page_latest=rev_id',
+                       ],
+                       __METHOD__,
+                       [ 'FOR UPDATE' ] // T51581
+               );
 
-               $title = Title::newFromID( $pageId );
-               $rec = self::getRevisionStore()->newNullRevision( $dbw, $title, 
$comment, $minor, $user );
+               if ( $current ) {
+                       if ( !$user ) {
+                               global $wgUser;
+                               $user = $wgUser;
+                       }
 
-               return new Revision( $rec );
+                       $row = [
+                               'page'       => $pageId,
+                               'user_text'  => $user->getName(),
+                               'user'       => $user->getId(),
+                               'comment'    => $summary,
+                               'minor_edit' => $minor,
+                               'text_id'    => $current->rev_text_id,
+                               'parent_id'  => $current->page_latest,
+                               'len'        => $current->rev_len,
+                               'sha1'       => $current->rev_sha1
+                       ];
+
+                       if ( $wgContentHandlerUseDB ) {
+                               $row['content_model'] = 
$current->rev_content_model;
+                               $row['content_format'] = 
$current->rev_content_format;
+                       }
+
+                       $row['title'] = Title::makeTitle( 
$current->page_namespace, $current->page_title );
+
+                       $revision = new Revision( $row );
+               } else {
+                       $revision = null;
+               }
+
+               return $revision;
        }
 
        /**
@@ -1054,13 +1948,35 @@
        public static function userCanBitfield( $bitfield, $field, User $user = 
null,
                Title $title = null
        ) {
-               global $wgUser;
-
-               if ( !$user ) {
-                       $user = $wgUser;
+               if ( $bitfield & $field ) { // aspect is deleted
+                       if ( $user === null ) {
+                               global $wgUser;
+                               $user = $wgUser;
+                       }
+                       if ( $bitfield & self::DELETED_RESTRICTED ) {
+                               $permissions = [ 'suppressrevision', 
'viewsuppressed' ];
+                       } elseif ( $field & self::DELETED_TEXT ) {
+                               $permissions = [ 'deletedtext' ];
+                       } else {
+                               $permissions = [ 'deletedhistory' ];
+                       }
+                       $permissionlist = implode( ', ', $permissions );
+                       if ( $title === null ) {
+                               wfDebug( "Checking for $permissionlist due to 
$field match on $bitfield\n" );
+                               return call_user_func_array( [ $user, 
'isAllowedAny' ], $permissions );
+                       } else {
+                               $text = $title->getPrefixedText();
+                               wfDebug( "Checking for $permissionlist on $text 
due to $field match on $bitfield\n" );
+                               foreach ( $permissions as $perm ) {
+                                       if ( $title->userCan( $perm, $user ) ) {
+                                               return true;
+                                       }
+                               }
+                               return false;
+                       }
+               } else {
+                       return true;
                }
-
-               return RevisionRecord::userCanBitfield( $bitfield, $field, 
$user, $title );
        }
 
        /**
@@ -1072,7 +1988,18 @@
         * @return string|bool False if not found
         */
        static function getTimestampFromId( $title, $id, $flags = 0 ) {
-               return self::getRevisionStore()->getTimestampFromId( $title, 
$id, $flags );
+               $db = ( $flags & self::READ_LATEST )
+                       ? wfGetDB( DB_MASTER )
+                       : wfGetDB( DB_REPLICA );
+               // Casting fix for databases that can't take '' for rev_id
+               if ( $id == '' ) {
+                       $id = 0;
+               }
+               $conds = [ 'rev_id' => $id ];
+               $conds['rev_page'] = $title->getArticleID();
+               $timestamp = $db->selectField( 'revision', 'rev_timestamp', 
$conds, __METHOD__ );
+
+               return ( $timestamp !== false ) ? wfTimestamp( TS_MW, 
$timestamp ) : false;
        }
 
        /**
@@ -1083,7 +2010,12 @@
         * @return int
         */
        static function countByPageId( $db, $id ) {
-               return self::getRevisionStore()->countRevisionsByPageId( $db, 
$id );
+               $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
+                       [ 'rev_page' => $id ], __METHOD__ );
+               if ( $row ) {
+                       return $row->revCount;
+               }
+               return 0;
        }
 
        /**
@@ -1094,7 +2026,11 @@
         * @return int
         */
        static function countByTitle( $db, $title ) {
-               return self::getRevisionStore()->countRevisionsByTitle( $db, 
$title );
+               $id = $title->getArticleID();
+               if ( $id ) {
+                       return self::countByPageId( $db, $id );
+               }
+               return 0;
        }
 
        /**
@@ -1114,11 +2050,28 @@
         * @return bool True if the given user was the only one to edit since 
the given timestamp
         */
        public static function userWasLastToEdit( $db, $pageId, $userId, $since 
) {
+               if ( !$userId ) {
+                       return false;
+               }
+
                if ( is_int( $db ) ) {
                        $db = wfGetDB( $db );
                }
 
-               return self::getRevisionStore()->userWasLastToEdit( $db, 
$pageId, $userId, $since );
+               $res = $db->select( 'revision',
+                       'rev_user',
+                       [
+                               'rev_page' => $pageId,
+                               'rev_timestamp > ' . $db->addQuotes( 
$db->timestamp( $since ) )
+                       ],
+                       __METHOD__,
+                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
+               foreach ( $res as $row ) {
+                       if ( $row->rev_user != $userId ) {
+                               return false;
+                       }
+               }
+               return true;
        }
 
        /**
@@ -1126,20 +2079,54 @@
         *
         * This method allows for the use of caching, though accessing anything 
that normally
         * requires permission checks (aside from the text) will trigger a 
small DB lookup.
-        * The title will also be loaded if $pageIdOrTitle is an integer ID.
+        * The title will also be lazy loaded, though setTitle() can be used to 
preload it.
         *
-        * @param IDatabase $db ignored!
-        * @param int|Title $pageIdOrTitle Page ID or Title object
-        * @param int $revId Known current revision of this page. Determined 
automatically if not given.
+        * @param IDatabase $db
+        * @param int $pageId Page ID
+        * @param int $revId Known current revision of this page
         * @return Revision|bool Returns false if missing
         * @since 1.28
         */
-       public static function newKnownCurrent( IDatabase $db, $pageIdOrTitle, 
$revId = 0 ) {
-               $title = $pageIdOrTitle instanceof Title
-                       ? $pageIdOrTitle
-                       : Title::newFromID( $pageIdOrTitle );
+       public static function newKnownCurrent( IDatabase $db, $pageId, $revId 
) {
+               $cache = 
MediaWikiServices::getInstance()->getMainWANObjectCache();
+               return $cache->getWithSetCallback(
+                       // Page/rev IDs passed in from DB to reflect history 
merges
+                       $cache->makeGlobalKey( 'revision', $db->getDomainID(), 
$pageId, $revId ),
+                       $cache::TTL_WEEK,
+                       function ( $curValue, &$ttl, array &$setOpts ) use ( 
$db, $pageId, $revId ) {
+                               $setOpts += Database::getCacheSetOptions( $db );
 
-               $record = self::getRevisionStore()->getKnownCurrentRevision( 
$title, $revId );
-               return $record ? new Revision( $record ) : false;
+                               $rev = Revision::loadFromPageId( $db, $pageId, 
$revId );
+                               // Reflect revision deletion and user renames
+                               if ( $rev ) {
+                                       $rev->mTitle = null; // mutable; 
lazy-load
+                                       $rev->mRefreshMutableFields = true;
+                               }
+
+                               return $rev ?: false; // don't cache negatives
+                       }
+               );
+       }
+
+       /**
+        * For cached revisions, make sure the user name and rev_deleted is 
up-to-date
+        */
+       private function loadMutableFields() {
+               if ( !$this->mRefreshMutableFields ) {
+                       return; // not needed
+               }
+
+               $this->mRefreshMutableFields = false;
+               $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, 
[], $this->mWiki );
+               $row = $dbr->selectRow(
+                       [ 'revision', 'user' ],
+                       [ 'rev_deleted', 'user_name' ],
+                       [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
+                       __METHOD__
+               );
+               if ( $row ) { // update values
+                       $this->mDeleted = (int)$row->rev_deleted;
+                       $this->mUserText = $row->user_name;
+               }
        }
 }
diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php
index 575970d..d21bcef 100644
--- a/includes/ServiceWiring.php
+++ b/includes/ServiceWiring.php
@@ -450,46 +450,6 @@
                return $factory;
        },
 
-       'RevisionStore' => function ( MediaWikiServices $services ) {
-               /** @var SqlBlobStore $blobStore */
-               $blobStore = $services->getService( '_SqlBlobStore' );
-
-               $store = new RevisionStore(
-                       $services->getDBLoadBalancer(),
-                       $blobStore,
-                       $services->getMainWANObjectCache()
-               );
-
-               $config = $services->getMainConfig();
-               $store->setContentHandlerUseDB( $config->get( 
'ContentHandlerUseDB' ) );
-
-               return $store;
-       },
-
-       'BlobStore' => function ( MediaWikiServices $services ) {
-               return $services->getService( '_SqlBlobStore' );
-       },
-
-       '_SqlBlobStore' => function ( MediaWikiServices $services ) {
-               global $wgContLang; // TODO: manage $wgContLang as a service
-
-               $store = new SqlBlobStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getMainWANObjectCache()
-               );
-
-               $config = $services->getMainConfig();
-               $store->setCompressRevisions( $config->get( 'CompressRevisions' 
) );
-               $store->setCacheExpiry( $config->get( 'RevisionCacheExpiry' ) );
-               $store->setUseExternalStore( $config->get( 
'DefaultExternalStore' ) !== false );
-
-               if ( $config->get( 'LegacyEncoding' ) ) {
-                       $store->setLegacyEncoding( $config->get( 
'LegacyEncoding' ), $wgContLang );
-               }
-
-               return $store;
-       },
-
        'ExternalStoreFactory' => function ( MediaWikiServices $services ) {
                $config = $services->getMainConfig();
 
diff --git a/includes/actions/HistoryAction.php 
b/includes/actions/HistoryAction.php
index 85e8db6..0e964bf 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -335,8 +335,8 @@
         * @return FeedItem
         */
        function feedItem( $row ) {
-               $rev = new Revision( $row, 0, $this->getTitle() );
-
+               $rev = new Revision( $row );
+               $rev->setTitle( $this->getTitle() );
                $text = FeedUtils::formatDiffRow(
                        $this->getTitle(),
                        $this->getTitle()->getPreviousRevisionID( $rev->getId() 
),
@@ -639,10 +639,12 @@
         */
        function historyLine( $row, $next, $notificationtimestamp = false,
                $latest = false, $firstInList = false ) {
-               $rev = new Revision( $row, 0, $this->getTitle() );
+               $rev = new Revision( $row );
+               $rev->setTitle( $this->getTitle() );
 
                if ( is_object( $next ) ) {
-                       $prevRev = new Revision( $next, 0, $this->getTitle() );
+                       $prevRev = new Revision( $next );
+                       $prevRev->setTitle( $this->getTitle() );
                } else {
                        $prevRev = null;
                }
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index d6e9b74..768f980 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -1048,7 +1048,8 @@
                if ( $titleObj->getLatestRevID() ) {
                        $revision = Revision::newKnownCurrent(
                                $dbr,
-                               $titleObj
+                               $titleObj->getArticleID(),
+                               $titleObj->getLatestRevID()
                        );
                } else {
                        $revision = false;
diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php
index 7498ca5..ff997ab 100644
--- a/includes/page/WikiPage.php
+++ b/includes/page/WikiPage.php
@@ -23,7 +23,6 @@
 use MediaWiki\Edit\PreparedEdit;
 use \MediaWiki\Logger\LoggerFactory;
 use \MediaWiki\MediaWikiServices;
-use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBError;
@@ -672,7 +671,7 @@
                        $revision = Revision::newFromPageId( $this->getId(), 
$latest, $flags );
                } else {
                        $dbr = wfGetDB( DB_REPLICA );
-                       $revision = Revision::newKnownCurrent( $dbr, 
$this->getTitle(), $latest );
+                       $revision = Revision::newKnownCurrent( $dbr, 
$this->getId(), $latest );
                }
 
                if ( $revision ) { // sanity
@@ -1265,11 +1264,8 @@
                        $conditions['page_latest'] = $lastRevision;
                }
 
-               $revId = $revision->getId();
-               Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 
0' );
-
                $row = [ /* SET */
-                       'page_latest'      => $revId,
+                       'page_latest'      => $revision->getId(),
                        'page_touched'     => $dbw->timestamp( 
$revision->getTimestamp() ),
                        'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
                        'page_is_redirect' => $rt !== null ? 1 : 0,
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 8986ddd..58f0c0c 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -3498,7 +3498,13 @@
         * @return Revision|bool False if missing
         */
        public static function statelessFetchRevision( Title $title, $parser = 
false ) {
-               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title 
);
+               $pageId = $title->getArticleID();
+               $revId = $title->getLatestRevID();
+
+               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), 
$pageId, $revId );
+               if ( $rev ) {
+                       $rev->setTitle( $title );
+               }
 
                return $rev;
        }
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php 
b/includes/resourceloader/ResourceLoaderWikiModule.php
index 6eddfc0..bebc188 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -183,10 +183,12 @@
         * @return Content|null
         */
        protected function getContentObj( Title $title ) {
-               $revision = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), 
$title );
+               $revision = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), 
$title->getArticleID(),
+                       $title->getLatestRevID() );
                if ( !$revision ) {
                        return null;
                }
+               $revision->setTitle( $title );
                $content = $revision->getContent( Revision::RAW );
                if ( !$content ) {
                        wfDebugLog( 'resourceloader', __METHOD__ . ': failed to 
load content of JS/CSS page!' );
diff --git a/includes/specials/SpecialNewpages.php 
b/includes/specials/SpecialNewpages.php
index 1639386..671ab6f 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -290,16 +290,15 @@
 
        /**
         * @param stdClass $result Result row from recent changes
-        * @param Title $title
-        * @return bool|Revision
+        * @return Revision|bool
         */
-       protected function revisionFromRcResult( stdClass $result, Title $title 
) {
+       protected function revisionFromRcResult( stdClass $result ) {
                return new Revision( [
                        'comment' => CommentStore::newKey( 'rc_comment' 
)->getComment( $result )->text,
                        'deleted' => $result->rc_deleted,
                        'user_text' => $result->rc_user_text,
                        'user' => $result->rc_user,
-               ], 0, $title );
+               ] );
        }
 
        /**
@@ -314,7 +313,8 @@
 
                // Revision deletion works on revisions,
                // so cast our recent change row to a revision row.
-               $rev = $this->revisionFromRcResult( $result, $title );
+               $rev = $this->revisionFromRcResult( $result );
+               $rev->setTitle( $title );
 
                $classes = [];
                $attribs = [ 'data-mw-revid' => $result->rev_id ];
diff --git a/tests/phpunit/includes/MediaWikiServicesTest.php 
b/tests/phpunit/includes/MediaWikiServicesTest.php
index 4d39f7b..a5c4688 100644
--- a/tests/phpunit/includes/MediaWikiServicesTest.php
+++ b/tests/phpunit/includes/MediaWikiServicesTest.php
@@ -7,8 +7,6 @@
 use MediaWiki\Services\SalvageableService;
 use MediaWiki\Services\ServiceDisabledException;
 use MediaWiki\Shell\CommandFactory;
-use MediaWiki\Storage\BlobStore;
-use MediaWiki\Storage\RevisionStore;
 
 /**
  * @covers MediaWiki\MediaWikiServices
@@ -333,8 +331,6 @@
                        'LocalServerObjectCache' => [ 'LocalServerObjectCache', 
BagOStuff::class ],
                        'VirtualRESTServiceClient' => [ 
'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
                        'ShellCommandFactory' => [ 'ShellCommandFactory', 
CommandFactory::class ],
-                       'BlobStore' => [ 'BlobStore', BlobStore::class ],
-                       'RevisionStore' => [ 'RevisionStore', 
RevisionStore::class ],
                ];
        }
 
diff --git a/tests/phpunit/includes/RevisionDbTestBase.php 
b/tests/phpunit/includes/RevisionDbTestBase.php
index 9ab76c8..91dbf2c 100644
--- a/tests/phpunit/includes/RevisionDbTestBase.php
+++ b/tests/phpunit/includes/RevisionDbTestBase.php
@@ -1,8 +1,4 @@
 <?php
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\IncompleteRevisionException;
-use MediaWiki\Storage\RevisionRecord;
 
 /**
  * RevisionDbTestBase contains test cases for the Revision class that have 
Database interactions.
@@ -76,7 +72,6 @@
                MWNamespace::clearCaches();
                // Reset namespace cache
                $wgContLang->resetNamespaces();
-
                if ( !$this->testPage ) {
                        /**
                         * We have to create a new page for each subclass as 
the page creation may result
@@ -107,24 +102,12 @@
                        $props['text'] = 'Lorem Ipsum';
                }
 
-               if ( !isset( $props['user_text'] ) ) {
-                       $props['user_text'] = 'Tester';
-               }
-
-               if ( !isset( $props['user'] ) ) {
-                       $props['user'] = 0;
-               }
-
                if ( !isset( $props['comment'] ) ) {
                        $props['comment'] = 'just a test';
                }
 
                if ( !isset( $props['page'] ) ) {
                        $props['page'] = $this->testPage->getId();
-               }
-
-               if ( !isset( $props['content_model'] ) ) {
-                       $props['content_model'] = CONTENT_MODEL_WIKITEXT;
                }
 
                $rev = new Revision( $props );
@@ -219,23 +202,14 @@
                $revId = $rev->insertOn( wfGetDB( DB_MASTER ) );
 
                $this->assertInternalType( 'integer', $revId );
-               $this->assertSame( $revId, $rev->getId() );
-
-               // getTextId() must be an int!
                $this->assertInternalType( 'integer', $rev->getTextId() );
-
-               $mainSlot = $rev->getRevisionRecord()->getSlot( 'main', 
RevisionRecord::RAW );
-
-               // we currently only support storage in the text table
-               $textId = MediaWikiServices::getInstance()
-                       ->getBlobStore()
-                       ->getTextIdFromAddress( $mainSlot->getAddress() );
+               $this->assertSame( $revId, $rev->getId() );
 
                $this->assertSelect(
                        'text',
                        [ 'old_id', 'old_text' ],
-                       "old_id = $textId",
-                       [ [ strval( $textId ), 'Revision Text' ] ]
+                       "old_id = {$rev->getTextId()}",
+                       [ [ strval( $rev->getTextId() ), 'Revision Text' ] ]
                );
                $this->assertSelect(
                        'revision',
@@ -254,7 +228,7 @@
                        [ [
                                strval( $rev->getId() ),
                                strval( $this->testPage->getId() ),
-                               strval( $textId ),
+                               strval( $rev->getTextId() ),
                                '0',
                                '0',
                                '0',
@@ -272,12 +246,11 @@
                // If an ExternalStore is set don't use it.
                $this->setMwGlobals( 'wgDefaultExternalStore', false );
                $this->setExpectedException(
-                       IncompleteRevisionException::class,
-                       "rev_page field must not be 0!"
+                       MWException::class,
+                       "Cannot insert revision: page ID must be nonzero"
                );
 
-               $title = Title::newFromText( 'Nonexistant-' . __METHOD__ );
-               $rev = new Revision( [], 0, $title );
+               $rev = new Revision( [] );
 
                $rev->insertOn( wfGetDB( DB_MASTER ) );
        }
@@ -350,37 +323,7 @@
                ];
                yield [
                        function ( $f ) {
-                               unset( $f['ar_text'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
                                unset( $f['ar_text_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_page_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_parent_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_rev_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_sha1'] );
                                return $f;
                        },
                ];
@@ -391,17 +334,6 @@
         * @covers Revision::newFromArchiveRow
         */
        public function testNewFromArchiveRow( $selectModifier ) {
-               $services = MediaWikiServices::getInstance();
-
-               $store = new RevisionStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getService( '_SqlBlobStore' ),
-                       $services->getMainWANObjectCache()
-               );
-
-               $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() 
);
-               $this->setService( 'RevisionStore', $store );
-
                $page = $this->createPage(
                        'RevisionStorageTest_testNewFromArchiveRow',
                        'Lorem Ipsum',
@@ -422,8 +354,6 @@
                $row = $res->fetchObject();
                $res->free();
 
-               // MCR migration note: $row is now required to contain ar_title 
and ar_namespace.
-               // Alternatively, a Title object can be passed to 
RevisionStore::newRevisionFromArchiveRow
                $rev = Revision::newFromArchiveRow( $row );
 
                $this->assertRevEquals( $orig, $rev );
@@ -452,7 +382,7 @@
                $row = $res->fetchObject();
                $res->free();
 
-               $rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 
'SOMEOVERRIDE' ] );
+               $rev = Revision::newFromArchiveRow( $row, [ 'comment' => 
'SOMEOVERRIDE' ] );
 
                $this->assertNotEquals( $orig->getComment(), $rev->getComment() 
);
                $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
@@ -496,8 +426,7 @@
         * @covers Revision::newFromPageId
         */
        public function testNewFromPageIdWithNotLatestId() {
-               $content = new WikitextContent( __METHOD__ );
-               $this->testPage->doEditContent( $content, __METHOD__ );
+               $this->testPage->doEditContent( new WikitextContent( __METHOD__ 
), __METHOD__ );
                $rev = Revision::newFromPageId(
                        $this->testPage->getId(),
                        $this->testPage->getRevision()->getPrevious()->getId()
@@ -518,7 +447,6 @@
                $this->testPage->doEditContent( new WikitextContent( __METHOD__ 
), __METHOD__ );
                $id = $this->testPage->getRevision()->getId();
 
-               $this->hideDeprecated( 'Revision::fetchRevision' );
                $res = Revision::fetchRevision( $this->testPage->getTitle() );
 
                # note: order is unspecified
@@ -527,7 +455,8 @@
                        $rows[$row->rev_id] = $row;
                }
 
-               $this->assertEmpty( $rows, 'expected empty set' );
+               $this->assertEquals( 1, count( $rows ), 'expected exactly one 
revision' );
+               $this->assertArrayHasKey( $id, $rows, 'missing revision with id 
' . $id );
        }
 
        /**
@@ -612,10 +541,6 @@
                        'new null revision should have a different id from the 
original revision' );
                $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
                        'new null revision should have the same text id as the 
original revision' );
-               $this->assertEquals( $orig->getSha1(), $rev->getSha1(),
-                       'new null revision should have the same SHA1 as the 
original revision' );
-               $this->assertTrue( $orig->getRevisionRecord()->hasSameContent( 
$rev->getRevisionRecord() ),
-                       'new null revision should have the same content as the 
original revision' );
                $this->assertEquals( __METHOD__, 
$rev->getContent()->getNativeData() );
        }
 
@@ -681,7 +606,7 @@
                        'user' => $userA->getId(),
                        'text' => 'zero',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit zero'
+                       'summary' => 'edit zero'
                ] );
                $revisions[0]->insertOn( $dbw );
 
@@ -693,7 +618,7 @@
                        'user' => $userA->getId(),
                        'text' => 'one',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit one'
+                       'summary' => 'edit one'
                ] );
                $revisions[1]->insertOn( $dbw );
 
@@ -704,7 +629,7 @@
                        'user' => $userB->getId(),
                        'text' => 'two',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit two'
+                       'summary' => 'edit two'
                ] );
                $revisions[2]->insertOn( $dbw );
 
@@ -715,7 +640,7 @@
                        'user' => $userA->getId(),
                        'text' => 'three',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit three'
+                       'summary' => 'edit three'
                ] );
                $revisions[3]->insertOn( $dbw );
 
@@ -726,23 +651,12 @@
                        'user' => $userA->getId(),
                        'text' => 'zero',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit four'
+                       'summary' => 'edit four'
                ] );
                $revisions[4]->insertOn( $dbw );
 
                // test it ---------------------------------
                $since = $revisions[$sinceIdx]->getTimestamp();
-
-               $allRows = iterator_to_array( $dbw->select(
-                       'revision',
-                       [ 'rev_id', 'rev_timestamp', 'rev_user' ],
-                       [
-                               'rev_page' => $page->getId(),
-                               //'rev_timestamp > ' . $dbw->addQuotes( 
$dbw->timestamp( $since ) )
-                       ],
-                       __METHOD__,
-                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ]
-               ) );
 
                $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), 
$userA->getId(), $since );
 
@@ -891,16 +805,12 @@
                        'text_id' => 123456789, // not in the test DB
                ] );
 
-               MediaWiki\suppressWarnings(); // bad text_id will trigger a 
warning.
-
                $this->assertNull( $rev->getContent(),
                        "getContent() should return null if the revision's text 
blob could not be loaded." );
 
                // NOTE: check this twice, once for lazy initialization, and 
once with the cached value.
                $this->assertNull( $rev->getContent(),
                        "getContent() should return null if the revision's text 
blob could not be loaded." );
-
-               MediaWiki\suppressWarnings( 'end' );
        }
 
        public function provideGetSize() {
@@ -994,7 +904,6 @@
         */
        public function testLoadFromId() {
                $rev = $this->testPage->getRevision();
-               $this->hideDeprecated( 'Revision::loadFromId' );
                $this->assertRevEquals(
                        $rev,
                        Revision::loadFromId( wfGetDB( DB_MASTER ), 
$rev->getId() )
@@ -1117,7 +1026,7 @@
                $rev[1] = $this->testPage->getLatest();
 
                $this->assertSame(
-                       [ $rev[1] => $textLength ],
+                       [ $rev[1] => strval( $textLength ) ],
                        Revision::getParentLengths(
                                wfGetDB( DB_MASTER ),
                                [ $rev[1] ]
@@ -1140,7 +1049,7 @@
                $rev[2] = $this->testPage->getLatest();
 
                $this->assertSame(
-                       [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength 
],
+                       [ $rev[1] => strval( $textOneLength ), $rev[2] => 
strval( $textTwoLength ) ],
                        Revision::getParentLengths(
                                wfGetDB( DB_MASTER ),
                                [ $rev[1], $rev[2] ]
@@ -1169,6 +1078,14 @@
                                $rev->getTitle()
                        )
                );
+       }
+
+       /**
+        * @covers Revision::getTitle
+        */
+       public function testGetTitle_forBadRevision() {
+               $rev = new Revision( [] );
+               $this->assertNull( $rev->getTitle() );
        }
 
        /**
@@ -1346,21 +1263,14 @@
                $rev = $this->testPage->getRevision();
 
                // Clear any previous cache for the revision during creation
-               $key = $cache->makeGlobalKey( 'revision-row-1.29',
-                       $db->getDomainID(),
-                       $rev->getPage(),
-                       $rev->getId()
-               );
+               $key = $cache->makeGlobalKey( 'revision', $db->getDomainID(), 
$rev->getPage(), $rev->getId() );
                $cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
                $this->assertFalse( $cache->get( $key ) );
 
                // Get the new revision and make sure it is in the cache and 
correct
                $newRev = Revision::newKnownCurrent( $db, $rev->getPage(), 
$rev->getId() );
                $this->assertRevEquals( $rev, $newRev );
-
-               $cachedRow = $cache->get( $key );
-               $this->assertNotFalse( $cachedRow );
-               $this->assertEquals( $rev->getId(), $cachedRow->rev_id );
+               $this->assertRevEquals( $rev, $cache->get( $key ) );
        }
 
        public function provideUserCanBitfield() {
@@ -1467,7 +1377,7 @@
                        ]
                );
                $user = $this->getTestUser( $userGroups )->getUser();
-               $revision = new Revision( [ 'deleted' => $bitField ], 0, 
$this->testPage->getTitle() );
+               $revision = new Revision( [ 'deleted' => $bitField ] );
 
                $this->assertSame(
                        $expected,
diff --git a/tests/phpunit/includes/RevisionTest.php 
b/tests/phpunit/includes/RevisionTest.php
index b7f1a47d..361984b 100644
--- a/tests/phpunit/includes/RevisionTest.php
+++ b/tests/phpunit/includes/RevisionTest.php
@@ -1,9 +1,6 @@
 <?php
 
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\SqlBlobStore;
-use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * Test cases in RevisionTest should not interact with the Database.
@@ -23,35 +20,6 @@
                                'content' => new JavaScriptContent( 'hellow 
world.' )
                        ],
                ];
-               // FIXME: test with and without user ID, and with a user object.
-               // We can't prepare that here though, since we don't yet have a 
dummy DB
-       }
-
-       /**
-        * @param string $model
-        * @return Title
-        */
-       public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT ) {
-               $mock = $this->getMockBuilder( Title::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $mock->expects( $this->any() )
-                       ->method( 'getNamespace' )
-                       ->will( $this->returnValue( 
$this->getDefaultWikitextNS() ) );
-               $mock->expects( $this->any() )
-                       ->method( 'getPrefixedText' )
-                       ->will( $this->returnValue( 'RevisionTest' ) );
-               $mock->expects( $this->any() )
-                       ->method( 'getDBKey' )
-                       ->will( $this->returnValue( 'RevisionTest' ) );
-               $mock->expects( $this->any() )
-                       ->method( 'getArticleID' )
-                       ->will( $this->returnValue( 23 ) );
-               $mock->expects( $this->any() )
-                       ->method( 'getModel' )
-                       ->will( $this->returnValue( $model ) );
-
-               return $mock;
        }
 
        /**
@@ -59,20 +27,11 @@
         * @covers Revision::__construct
         * @covers Revision::constructFromRowArray
         */
-       public function testConstructFromArray( $rowArray ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+       public function testConstructFromArray( array $rowArray ) {
+               $rev = new Revision( $rowArray );
                $this->assertNotNull( $rev->getContent(), 'no content object 
available' );
                $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, 
$rev->getContent()->getModel() );
                $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, 
$rev->getContentModel() );
-       }
-
-       /**
-        * @covers Revision::__construct
-        * @covers Revision::constructFromRowArray
-        */
-       public function testConstructFromEmptyArray() {
-               $rev = new Revision( [], 0, $this->getMockTitle() );
-               $this->assertNull( $rev->getContent(), 'no content object 
should be available' );
        }
 
        public function provideConstructFromArray_userSetAsExpected() {
@@ -93,13 +52,23 @@
                        99,
                        'SomeTextUserName',
                ];
-               yield 'user text only' => [
+               // Note: the below XXX test cases are odd and probably result 
in unexpected behaviour if used
+               // in production code.
+               yield 'XXX: user text only' => [
                        [
                                'content' => new JavaScriptContent( 'hello 
world.' ),
                                'user_text' => '111.111.111.111',
                        ],
-                       0,
+                       null,
                        '111.111.111.111',
+               ];
+               yield 'XXX: user id only' => [
+                       [
+                               'content' => new JavaScriptContent( 'hello 
world.' ),
+                               'user' => 9989,
+                       ],
+                       9989,
+                       null,
                ];
        }
 
@@ -126,7 +95,7 @@
                        $expectedUserName = $testUser->getName();
                }
 
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertEquals( $expectedUserId, $rev->getUser() );
                $this->assertEquals( $expectedUserName, $rev->getUserText() );
        }
@@ -136,37 +105,28 @@
                        [
                                'content' => new WikitextContent( 'GOAT' ),
                                'text_id' => 'someid',
-                       ],
+                               ],
                        new MWException( "Text already stored in external store 
(id someid), " .
                                "can't serialize content object" )
                ];
-               yield 'unknown user id and no user name' => [
-                       [
-                               'content' => new JavaScriptContent( 'hello 
world.' ),
-                               'user' => 9989,
-                       ],
-                       new MWException( 'user_text not given, and unknown user 
ID 9989' )
-               ];
                yield 'with bad content object (class)' => [
                        [ 'content' => new stdClass() ],
-                       new MWException( 'content field must contain a Content 
object.' )
+                       new MWException( '`content` field must contain a 
Content object.' )
                ];
                yield 'with bad content object (string)' => [
                        [ 'content' => 'ImAGoat' ],
-                       new MWException( 'content field must contain a Content 
object.' )
+                       new MWException( '`content` field must contain a 
Content object.' )
                ];
                yield 'bad row format' => [
                        'imastring, not a row',
-                       new InvalidArgumentException(
-                               '$row must be a row object, an associative 
array, or a RevisionRecord'
-                       )
+                       new MWException( 'Revision constructor passed invalid 
row format.' )
                ];
        }
 
        /**
         * @dataProvider provideConstructFromArrayThrowsExceptions
         * @covers Revision::__construct
-        * @covers RevisionStore::newMutableRevisionFromArray
+        * @covers Revision::constructFromRowArray
         */
        public function testConstructFromArrayThrowsExceptions( $rowArray, 
Exception $expectedException ) {
                $this->setExpectedException(
@@ -174,25 +134,14 @@
                        $expectedException->getMessage(),
                        $expectedException->getCode()
                );
-               new Revision( $rowArray, 0, $this->getMockTitle() );
-       }
-
-       /**
-        * @covers Revision::__construct
-        * @covers RevisionStore::newMutableRevisionFromArray
-        */
-       public function testConstructFromNothing() {
-               $this->setExpectedException(
-                       InvalidArgumentException::class
-               );
-               new Revision( [] );
+               new Revision( $rowArray );
        }
 
        public function provideConstructFromRow() {
                yield 'Full construction' => [
                        [
-                               'rev_id' => '42',
-                               'rev_page' => '23',
+                               'rev_id' => '2',
+                               'rev_page' => '1',
                                'rev_text_id' => '2',
                                'rev_timestamp' => '20171017114835',
                                'rev_user_text' => '127.0.0.1',
@@ -209,8 +158,8 @@
                                'rev_content_model' => 'GOATMODEL',
                        ],
                        function ( RevisionTest $testCase, Revision $rev ) {
-                               $testCase->assertSame( 42, $rev->getId() );
-                               $testCase->assertSame( 23, $rev->getPage() );
+                               $testCase->assertSame( 2, $rev->getId() );
+                               $testCase->assertSame( 1, $rev->getPage() );
                                $testCase->assertSame( 2, $rev->getTextId() );
                                $testCase->assertSame( '20171017114835', 
$rev->getTimestamp() );
                                $testCase->assertSame( '127.0.0.1', 
$rev->getUserText() );
@@ -225,10 +174,10 @@
                                $testCase->assertSame( 'GOATMODEL', 
$rev->getContentModel() );
                        }
                ];
-               yield 'default field values' => [
+               yield 'null fields' => [
                        [
-                               'rev_id' => '42',
-                               'rev_page' => '23',
+                               'rev_id' => '2',
+                               'rev_page' => '1',
                                'rev_text_id' => '2',
                                'rev_timestamp' => '20171017114835',
                                'rev_user_text' => '127.0.0.1',
@@ -240,24 +189,11 @@
                                'rev_comment_cid' => null,
                        ],
                        function ( RevisionTest $testCase, Revision $rev ) {
-                               // parent ID may be null
-                               $testCase->assertSame( null, 
$rev->getParentId(), 'revision id' );
-
-                               // given fields
-                               $testCase->assertSame( $rev->getTimestamp(), 
'20171017114835', 'timestamp' );
-                               $testCase->assertSame( $rev->getUserText(), 
'127.0.0.1', 'user name' );
-                               $testCase->assertSame( $rev->getUser(), 0, 
'user id' );
-                               $testCase->assertSame( $rev->getComment(), 
'Goat Comment!' );
-                               $testCase->assertSame( false, $rev->isMinor(), 
'minor edit' );
-                               $testCase->assertSame( 0, 
$rev->getVisibility(), 'visibility flags' );
-
-                               // computed fields
-                               $testCase->assertNotNull( $rev->getSize(), 
'size' );
-                               $testCase->assertNotNull( $rev->getSha1(), 
'hash' );
-
-                               // NOTE: model and format will be detected 
based on the namespace of the (mock) title
-                               $testCase->assertSame( 'text/x-wiki', 
$rev->getContentFormat(), 'format' );
-                               $testCase->assertSame( 'wikitext', 
$rev->getContentModel(), 'model' );
+                               $testCase->assertNull( $rev->getSize() );
+                               $testCase->assertNull( $rev->getParentId() );
+                               $testCase->assertNull( $rev->getSha1() );
+                               $testCase->assertSame( 'text/x-wiki', 
$rev->getContentFormat() );
+                               $testCase->assertSame( 'wikitext', 
$rev->getContentModel() );
                        }
                ];
        }
@@ -265,34 +201,11 @@
        /**
         * @dataProvider provideConstructFromRow
         * @covers Revision::__construct
-        * @covers RevisionStore::newRevisionFromRow
+        * @covers Revision::constructFromDbRowObject
         */
        public function testConstructFromRow( array $arrayData, $assertions ) {
-               $data = 'Hello goat.'; // needs to match model and format
-
-               $blobStore = $this->getMockBuilder( SqlBlobStore::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $blobStore->method( 'getBlob' )
-                       ->will( $this->returnValue( $data ) );
-
-               $blobStore->method( 'getTextIdFromAddress' )
-                       ->will( $this->returnCallback(
-                               function ( $address ) {
-                                       // Turn "tt:1234" into 12345.
-                                       // Note that this must be functional so 
we can test getTextId().
-                                       // Ideally, we'd un-mock 
getTextIdFromAddress and use its actual implementation.
-                                       $parts = explode( ':', $address );
-                                       return (int)array_pop( $parts );
-                               }
-                       ) );
-
-               // Note override internal service, so RevisionStore uses it as 
well.
-               $this->setService( '_SqlBlobStore', $blobStore );
-
                $row = (object)$arrayData;
-               $rev = new Revision( $row, 0, $this->getMockTitle() );
+               $rev = new Revision( $row );
                $assertions( $this, $rev );
        }
 
@@ -322,7 +235,7 @@
         * @covers Revision::getId
         */
        public function testGetId( $rowArray, $expectedId ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertEquals( $expectedId, $rev->getId() );
        }
 
@@ -336,7 +249,7 @@
         * @covers Revision::setId
         */
        public function testSetId( $input, $expected ) {
-               $rev = new Revision( [], 0, $this->getMockTitle() );
+               $rev = new Revision( [] );
                $rev->setId( $input );
                $this->assertSame( $expected, $rev->getId() );
        }
@@ -351,7 +264,7 @@
         * @covers Revision::setUserIdAndName
         */
        public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
-               $rev = new Revision( [], 0, $this->getMockTitle() );
+               $rev = new Revision( [] );
                $rev->setUserIdAndName( $inputId, $name );
                $this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) 
);
                $this->assertEquals( $name, $rev->getUserText( Revision::RAW ) 
);
@@ -368,7 +281,7 @@
         * @covers Revision::getTextId()
         */
        public function testGetTextId( $rowArray, $expected ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertSame( $expected, $rev->getTextId() );
        }
 
@@ -383,7 +296,7 @@
         * @covers Revision::getParentId()
         */
        public function testGetParentId( $rowArray, $expected ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertSame( $expected, $rev->getParentId() );
        }
 
@@ -416,44 +329,9 @@
                $this->testGetRevisionText( $expected, $rowData );
        }
 
-       private function getWANObjectCache() {
-               return new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
-       }
-
-       /**
-        * @return SqlBlobStore
-        */
-       private function getBlobStore() {
-               /** @var LoadBalancer $lb */
-               $lb = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $cache = $this->getWANObjectCache();
-
-               $blobStore = new SqlBlobStore( $lb, $cache );
-               return $blobStore;
-       }
-
-       /**
-        * @return RevisionStore
-        */
-       private function getRevisionStore() {
-               /** @var LoadBalancer $lb */
-               $lb = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $cache = $this->getWANObjectCache();
-
-               $blobStore = new RevisionStore( $lb, $this->getBlobStore(), 
$cache );
-               return $blobStore;
-       }
-
        public function provideGetRevisionTextWithLegacyEncoding() {
                yield 'Utf8Native' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'utf-8',
@@ -462,7 +340,6 @@
                ];
                yield 'Utf8Legacy' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => '',
@@ -475,11 +352,8 @@
         * @covers Revision::getRevisionText
         * @dataProvider provideGetRevisionTextWithLegacyEncoding
         */
-       public function testGetRevisionWithLegacyEncoding( $expected, $lang, 
$encoding, $rowData ) {
-               $blobStore = $this->getBlobStore();
-               $blobStore->setLegacyEncoding( $encoding, Language::factory( 
$lang ) );
-               $this->setService( 'BlobStore', $blobStore );
-
+       public function testGetRevisionWithLegacyEncoding( $expected, 
$encoding, $rowData ) {
+               $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
                $this->testGetRevisionText( $expected, $rowData );
        }
 
@@ -491,7 +365,6 @@
                 */
                yield 'Utf8NativeGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'gzip,utf-8',
@@ -500,7 +373,6 @@
                ];
                yield 'Utf8LegacyGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'gzip',
@@ -513,13 +385,9 @@
         * @covers Revision::getRevisionText
         * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
         */
-       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, 
$lang, $encoding, $rowData ) {
+       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, 
$encoding, $rowData ) {
                $this->checkPHPExtension( 'zlib' );
-
-               $blobStore = $this->getBlobStore();
-               $blobStore->setLegacyEncoding( $encoding, Language::factory( 
$lang ) );
-               $this->setService( 'BlobStore', $blobStore );
-
+               $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
                $this->testGetRevisionText( $expected, $rowData );
        }
 
@@ -545,10 +413,7 @@
         */
        public function testCompressRevisionTextUtf8Gzip() {
                $this->checkPHPExtension( 'zlib' );
-
-               $blobStore = $this->getBlobStore();
-               $blobStore->setCompressBlobs( true );
-               $this->setService( 'BlobStore', $blobStore );
+               $this->setMwGlobals( 'wgCompressRevisions', true );
 
                $row = new stdClass;
                $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
@@ -563,41 +428,20 @@
                        Revision::getRevisionText( $row ), "getRevisionText" );
        }
 
+       public function provideFetchFromConds() {
+               yield [ 0, [] ];
+               yield [ Revision::READ_LOCKING, [ 'FOR UPDATE' ] ];
+       }
+
        /**
-        * @covers Revision::loadFromTitle
+        * @dataProvider provideFetchFromConds
+        * @covers Revision::fetchFromConds
         */
-       public function testLoadFromTitle() {
-               $title = $this->getMockTitle();
-
-               $conditions = [
-                       'rev_id=page_latest',
-                       'page_namespace' => $title->getNamespace(),
-                       'page_title' => $title->getDBkey()
-               ];
-
-               $row = (object)[
-                       'rev_id' => '42',
-                       'rev_page' => $title->getArticleID(),
-                       'rev_text_id' => '2',
-                       'rev_timestamp' => '20171017114835',
-                       'rev_user_text' => '127.0.0.1',
-                       'rev_user' => '0',
-                       'rev_minor_edit' => '0',
-                       'rev_deleted' => '0',
-                       'rev_len' => '46',
-                       'rev_parent_id' => '1',
-                       'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
-                       'rev_comment_text' => 'Goat Comment!',
-                       'rev_comment_data' => null,
-                       'rev_comment_cid' => null,
-                       'rev_content_format' => 'GOATFORMAT',
-                       'rev_content_model' => 'GOATMODEL',
-               ];
+       public function testFetchFromConds( $flags, array $options ) {
+               $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', 
MIGRATION_OLD );
+               $conditions = [ 'conditionsArray' ];
 
                $db = $this->getMock( IDatabase::class );
-               $db->expects( $this->any() )
-                       ->method( 'getDomainId' )
-                       ->will( $this->returnValue( wfWikiID() ) );
                $db->expects( $this->once() )
                        ->method( 'selectRow' )
                        ->with(
@@ -606,24 +450,17 @@
                                $this->isType( 'array' ),
                                $this->equalTo( $conditions ),
                                // Method name
-                               $this->stringContains( 
'fetchRevisionRowFromConds' ),
-                               // We don't really care about the options here
-                               $this->isType( 'array' ),
+                               $this->equalTo( 'Revision::fetchFromConds' ),
+                               $this->equalTo( $options ),
                                // We don't really care about the join conds 
are they come from the joinCond methods
                                $this->isType( 'array' )
                        )
-                       ->willReturn( $row );
+                       ->willReturn( 'RETURNVALUE' );
 
-               $revision = Revision::loadFromTitle( $db, $title );
+               $wrapper = TestingAccessWrapper::newFromClass( Revision::class 
);
+               $result = $wrapper->fetchFromConds( $db, $conditions, $flags );
 
-               $this->assertEquals( $title->getArticleID(), 
$revision->getTitle()->getArticleID() );
-               $this->assertEquals( $row->rev_id, $revision->getId() );
-               $this->assertEquals( $row->rev_len, $revision->getSize() );
-               $this->assertEquals( $row->rev_sha1, $revision->getSha1() );
-               $this->assertEquals( $row->rev_parent_id, 
$revision->getParentId() );
-               $this->assertEquals( $row->rev_timestamp, 
$revision->getTimestamp() );
-               $this->assertEquals( $row->rev_comment_text, 
$revision->getComment() );
-               $this->assertEquals( $row->rev_user_text, 
$revision->getUserText() );
+               $this->assertEquals( 'RETURNVALUE', $result );
        }
 
        public function provideDecompressRevisionText() {
@@ -688,12 +525,8 @@
         * @param mixed $expected
         */
        public function testDecompressRevisionText( $legacyEncoding, $text, 
$flags, $expected ) {
-               $blobStore = $this->getBlobStore();
-               if ( $legacyEncoding ) {
-                       $blobStore->setLegacyEncoding( $legacyEncoding, 
Language::factory( 'en' ) );
-               }
-
-               $this->setService( 'BlobStore', $blobStore );
+               $this->setMwGlobals( 'wgLegacyEncoding', $legacyEncoding );
+               $this->setMwGlobals( 'wgLanguageCode', 'en' );
                $this->assertSame(
                        $expected,
                        Revision::decompressRevisionText( $text, $flags )
@@ -789,20 +622,14 @@
         * @covers Revision::getRevisionText
         */
        public function testGetRevisionText_external_oldId() {
-               $cache = $this->getWANObjectCache();
+               $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] 
);
                $this->setService( 'MainWANObjectCache', $cache );
-
                $this->setService(
                        'ExternalStoreFactory',
                        new ExternalStoreFactory( [ 'ForTesting' ] )
                );
 
-               $lb = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $blobStore = new SqlBlobStore( $lb, $cache );
-               $this->setService( 'BlobStore', $blobStore );
+               $cacheKey = $cache->makeKey( 'revisiontext', 'textid', '7777' );
 
                $this->assertSame(
                        'AAAABBAAA',
@@ -814,8 +641,6 @@
                                ]
                        )
                );
-
-               $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 
'tt:7777' );
                $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
        }
 
@@ -1011,8 +836,6 @@
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1041,8 +864,6 @@
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1076,8 +897,6 @@
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1114,8 +933,6 @@
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1152,8 +969,6 @@
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1185,11 +1000,6 @@
         */
        public function testGetArchiveQueryInfo( $globals, $expected ) {
                $this->setMwGlobals( $globals );
-
-               $revisionStore = $this->getRevisionStore();
-               $revisionStore->setContentHandlerUseDB( 
$globals['wgContentHandlerUseDB'] );
-               $this->setService( 'RevisionStore', $revisionStore );
-
                $this->assertEquals(
                        $expected,
                        Revision::getArchiveQueryInfo()
@@ -1541,11 +1351,6 @@
         */
        public function testGetQueryInfo( $globals, $options, $expected ) {
                $this->setMwGlobals( $globals );
-
-               $revisionStore = $this->getRevisionStore();
-               $revisionStore->setContentHandlerUseDB( 
$globals['wgContentHandlerUseDB'] );
-               $this->setService( 'RevisionStore', $revisionStore );
-
                $this->assertEquals(
                        $expected,
                        Revision::getQueryInfo( $options )

-- 
To view, visit https://gerrit.wikimedia.org/r/399156
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Idf3404f3fa8f8d08a7fb2ab8268726e2c1edecfe
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: Addshore <addshorew...@gmail.com>
Gerrit-Reviewer: Anomie <bjor...@wikimedia.org>
Gerrit-Reviewer: Bene <benestar.wikime...@gmail.com>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Cicalese <ccical...@wikimedia.org>
Gerrit-Reviewer: Ladsgroup <ladsgr...@gmail.com>
Gerrit-Reviewer: Legoktm <lego...@member.fsf.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to