Daniel Kinzler has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/375050 )

Change subject: Turn Revision into a proxy to the new code.
......................................................................

Turn Revision into a proxy to the new code.

Change-Id: I4f24e7fbb683cb51f3fd8b250732bae9c7541ba2
---
M includes/Revision.php
M includes/Storage/DefaultRevisionRecord.php
M includes/Storage/RevisionLookup.php
M includes/Storage/RevisionStore.php
4 files changed, 190 insertions(+), 1,262 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/50/375050/1

diff --git a/includes/Revision.php b/includes/Revision.php
index e457beb..61b8b24 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -20,6 +20,11 @@
  * @file
  */
 
+use MediaWiki\Storage\ContentStore;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionSlots;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SlotRecord;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Linker\LinkTarget;
@@ -28,78 +33,46 @@
 use Wikimedia\Rdbms\FakeResultWrapper;
 
 /**
- * @todo document
+ * @deprecated since 1.30, use RevisionRecord, RevisionStore, and ContentStore 
instead.
  */
 class Revision implements IDBAccessObject {
-       /** @var int|null */
+       /** @var int|null Needed on top of $mRecord, bnecause it may be 
updated! */
        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 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 RevisionRecord */
+       protected $mRecord;
 
        /** @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 = 1;
-       const DELETED_COMMENT = 2;
-       const DELETED_USER = 4;
-       const DELETED_RESTRICTED = 8;
-       const SUPPRESSED_USER = 12; // convenience
-       const SUPPRESSED_ALL = 15; // convenience
+       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;
 
        // Audience options for accessors
-       const FOR_PUBLIC = 1;
-       const FOR_THIS_USER = 2;
-       const RAW = 3;
+       const FOR_PUBLIC = RevisionRecord::FOR_PUBLIC;
+       const FOR_THIS_USER = RevisionRecord::FOR_THIS_USER;
+       const RAW = RevisionRecord::RAW;
 
-       const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and 
max key count
+       const TEXT_CACHE_GROUP = ContentStore::TEXT_CACHE_GROUP;
+
+       /**
+        * @return RevisionStore
+        */
+       protected static function getRevisionStore() {
+               MediaWikiServices::getInstance()->getRevisionStore();
+       }
+
+       /**
+        * @return ContentStore
+        */
+       protected static function getContentStore() {
+               MediaWikiServices::getInstance()->getContentStore();
+       }
 
        /**
         * Load a page revision from a given revision ID number.
@@ -114,7 +87,8 @@
         * @return Revision|null
         */
        public static function newFromId( $id, $flags = 0 ) {
-               return self::newFromConds( [ 'rev_id' => intval( $id ) ], 
$flags );
+               $rec = self::getRevisionStore()->getRevisionById( $id, $flags );
+               return $rec === null ? null : new Revision( $rec, $flags );
        }
 
        /**
@@ -132,20 +106,8 @@
         * @return Revision|null
         */
        public static function newFromTitle( LinkTarget $linkTarget, $id = 0, 
$flags = 0 ) {
-               $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 );
-               }
+               $rec = self::getRevisionStore()->getRevisionByTitle( 
$linkTarget, $id, $flags );
+               return $rec === null ? null : new Revision( $rec, $flags );
        }
 
        /**
@@ -163,16 +125,8 @@
         * @return Revision|null
         */
        public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) 
{
-               $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 );
-               }
+               $rec = self::getRevisionStore()->getRevisionByPageId( $pageId, 
$revId, $flags );
+               return $rec === null ? null : new Revision( $rec, $flags );
        }
 
        /**
@@ -187,44 +141,8 @@
         * @return Revision
         */
        public static function newFromArchiveRow( $row, $overrides = [] ) {
-               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'    => $row->ar_comment,
-                       '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 );
+               $rec = self::getRevisionStore()->newRevisionFromArchiveRow( 
$row, $overrides );
+               return new Revision( $rec );
        }
 
        /**
@@ -234,19 +152,23 @@
         * @return Revision
         */
        public static function newFromRow( $row ) {
-               return new self( $row );
+               $rec = self::getRevisionStore()->newRevisionFromRow_1_29( $row 
);
+               return new Revision( $rec );
        }
 
        /**
         * Load a page revision from a given revision ID number.
         * Returns null if no such revision can be found.
         *
+        * @deprecated since 1.30, because there are no known callers.
+        *
         * @param IDatabase $db
         * @param int $id
         * @return Revision|null
         */
        public static function loadFromId( $db, $id ) {
-               return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] 
);
+               $rec = self::getRevisionStore()->loadRevisionFromId( $db, $id );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -260,13 +182,8 @@
         * @return Revision|null
         */
        public static function loadFromPageId( $db, $pageid, $id = 0 ) {
-               $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 );
+               $rec = self::getRevisionStore()->loadRevisionFromPageId( $db, 
$pageid, $id );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -280,18 +197,8 @@
         * @return Revision|null
         */
        public static function loadFromTitle( $db, $title, $id = 0 ) {
-               if ( $id ) {
-                       $matchId = intval( $id );
-               } else {
-                       $matchId = 'page_latest';
-               }
-               return self::loadFromConds( $db,
-                       [
-                               "rev_id=$matchId",
-                               'page_namespace' => $title->getNamespace(),
-                               'page_title' => $title->getDBkey()
-                       ]
-               );
+               $rec = self::getRevisionStore()->loadRevisionFromTitle( $db, 
$title, $id );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -305,67 +212,8 @@
         * @return Revision|null
         */
        public static function loadFromTimestamp( $db, $title, $timestamp ) {
-               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->getWikiID();
-
-                       return $rev;
-               }
-
-               return null;
+               $rec = self::getRevisionStore()->loadRevisionFromTimestamp( 
$db, $title, $timestamp );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -375,49 +223,11 @@
         *
         * @param LinkTarget $title
         * @return ResultWrapper
-        * @deprecated Since 1.28
+        * @deprecated Since 1.28, no callers in core. No-op since 1.30.
         */
        public static function fetchRevision( LinkTarget $title ) {
-               $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 ) {
-               $fields = array_merge(
-                       self::selectFields(),
-                       self::selectPageFields(),
-                       self::selectUserFields()
-               );
-               $options = [];
-               if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
-                       $options[] = 'FOR UPDATE';
-               }
-               return $db->selectRow(
-                       [ 'revision', 'page', 'user' ],
-                       $fields,
-                       $conditions,
-                       __METHOD__,
-                       $options,
-                       [ 'page' => self::pageJoinCond(), 'user' => 
self::userJoinCond() ]
-               );
+               wfDeprecated( __METHOD__, '1.30' );
+               return new FakeResultWrapper( [] );
        }
 
        /**
@@ -427,7 +237,7 @@
         * @return array
         */
        public static function userJoinCond() {
-               return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] 
];
+               return self::getRevisionStore()->userJoinCond();
        }
 
        /**
@@ -437,7 +247,7 @@
         * @return array
         */
        public static function pageJoinCond() {
-               return [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
+               return self::getRevisionStore()->pageJoinCond();
        }
 
        /**
@@ -446,29 +256,7 @@
         * @return array
         */
        public static function selectFields() {
-               global $wgContentHandlerUseDB;
-
-               $fields = [
-                       'rev_id',
-                       'rev_page',
-                       'rev_text_id',
-                       'rev_timestamp',
-                       'rev_comment',
-                       'rev_user_text',
-                       'rev_user',
-                       'rev_minor_edit',
-                       'rev_deleted',
-                       'rev_len',
-                       'rev_parent_id',
-                       'rev_sha1',
-               ];
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'rev_content_format';
-                       $fields[] = 'rev_content_model';
-               }
-
-               return $fields;
+               return self::getRevisionStore()->selectRevisionFields();
        }
 
        /**
@@ -477,29 +265,7 @@
         * @return array
         */
        public static function selectArchiveFields() {
-               global $wgContentHandlerUseDB;
-               $fields = [
-                       'ar_id',
-                       'ar_page_id',
-                       'ar_rev_id',
-                       'ar_text',
-                       'ar_text_id',
-                       'ar_timestamp',
-                       'ar_comment',
-                       'ar_user_text',
-                       'ar_user',
-                       'ar_minor_edit',
-                       'ar_deleted',
-                       'ar_len',
-                       'ar_parent_id',
-                       'ar_sha1',
-               ];
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'ar_content_format';
-                       $fields[] = 'ar_content_model';
-               }
-               return $fields;
+               return self::getRevisionStore()->selectArchiveFields();
        }
 
        /**
@@ -508,10 +274,7 @@
         * @return array
         */
        public static function selectTextFields() {
-               return [
-                       'old_text',
-                       'old_flags'
-               ];
+               return self::getRevisionStore()->selectTextFields();
        }
 
        /**
@@ -519,14 +282,7 @@
         * @return array
         */
        public static function selectPageFields() {
-               return [
-                       'page_namespace',
-                       'page_title',
-                       'page_id',
-                       'page_latest',
-                       'page_is_redirect',
-                       'page_len',
-               ];
+               return self::getRevisionStore()->selectPageFields();
        }
 
        /**
@@ -534,7 +290,7 @@
         * @return array
         */
        public static function selectUserFields() {
-               return [ 'user_name' ];
+               return self::getRevisionStore()->selectUserFields();
        }
 
        /**
@@ -544,18 +300,7 @@
         * @return array
         */
        public static function getParentLengths( $db, array $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;
+               return self::getRevisionStore()->getParentLengths( $db, $revIds 
);
        }
 
        /**
@@ -563,166 +308,18 @@
         * @throws MWException
         * @access private
         */
-       function __construct( $row ) {
-               if ( is_object( $row ) ) {
-                       $this->mId = intval( $row->rev_id );
-                       $this->mPage = intval( $row->rev_page );
-                       $this->mTextId = intval( $row->rev_text_id );
-                       $this->mComment = $row->rev_comment;
-                       $this->mUser = intval( $row->rev_user );
-                       $this->mMinorEdit = intval( $row->rev_minor_edit );
-                       $this->mTimestamp = $row->rev_timestamp;
-                       $this->mDeleted = intval( $row->rev_deleted );
+       function __construct( $row, $queryFlags = 0 ) {
+               $this->mQueryFlags = 0;
 
-                       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;
+               if ( $row instanceof RevisionRecord ) {
+                       $this->mRecord = $row;
                } elseif ( is_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'] ) ) {
-                               // @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'] ) ) {
-                               if ( !( $row['content'] instanceof Content ) ) {
-                                       throw new MWException( '`content` field 
must contain a Content object.' );
-                               }
-
-                               $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();
+                       $this->mRecord = 
self::getRevisionStore()->newRevisionFromArray_1_29( $row );
                } else {
-                       throw new MWException( 'Revision constructor passed 
invalid row format.' );
+                       $this->mRecord = 
self::getRevisionStore()->newRevisionFromRow_1_29( $row );
                }
-               $this->mUnpatrolled = null;
+
+               $this->mId = $this->mRecord->getId();
        }
 
        /**
@@ -743,6 +340,7 @@
         * @param int $id
         */
        public function setId( $id ) {
+               // TODO: only if ID was null previously?
                $this->mId = (int)$id;
        }
 
@@ -756,18 +354,35 @@
         * @param string $name User name
         */
        public function setUserIdAndName( $id, $name ) {
-               $this->mUser = (int)$id;
-               $this->mUserText = $name;
-               $this->mOrigUserText = $name;
+               throw new MWException( 'Not Implemented!' );
+               // FIXME: RevisionRecord is immutable, we'll need to create a 
new instance!
+               // $this->mUser = (int)$id;
+               // $this->mUserText = $name;
+               // $this->mOrigUserText = $name;
        }
 
        /**
-        * Get text row ID
+        * @return SlotRecord
+        */
+       private function getMainSlot() {
+               // XXX: make getSlot part of the RevisionRecord interface?
+               $this->mRecord->getSlot( 'main' );
+       }
+
+       /**
+        * Get the main slot's content address.
         *
-        * @return int|null
+        * @warn Since 1.30, this returns the main slot's content address as a 
string.
+        * Previously, this used to return the ID of the text row as an int.
+        * This change was made in the light of the fact that calling code 
seems to use the
+        * return value solely to check whether two revisions have the same 
content.
+        *
+        * @return string|null
         */
        public function getTextId() {
-               return $this->mTextId;
+               // FIXME: or find the text row ID afterall?
+               $slot = $this->getMainSlot();
+               return $slot->getAddress();
        }
 
        /**
@@ -776,25 +391,26 @@
         * @return int|null
         */
        public function getParentId() {
-               return $this->mParentId;
+               return $this->mRecord->getParentId();
        }
 
        /**
         * Returns the length of the text in this revision, or null if unknown.
         *
-        * @return int|null
+        * @return int
         */
        public function getSize() {
-               return $this->mSize;
+               return $this->mRecord->getSize();
        }
 
        /**
-        * Returns the base36 sha1 of the text in this revision, or null if 
unknown.
+        * Returns the base36 sha1 of the content in this revision, or null if 
unknown.
         *
-        * @return string|null
+        * @return string
         */
        public function getSha1() {
-               return $this->mSha1;
+               // XXX: we may want to drop all the hashing logic, it's not 
worth the overhead.
+               return $this->mRecord->getSha1();
        }
 
        /**
@@ -805,32 +421,8 @@
         * @return Title|null
         */
        public function getTitle() {
-               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 );
-                       $row = $dbr->selectRow(
-                               [ 'page', 'revision' ],
-                               self::selectPageFields(),
-                               [ 'page_id=rev_page', 'rev_id' => $this->mId ],
-                               __METHOD__
-                       );
-                       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;
+               // XXX: this really is not very pretty. Maybe move getTitle 
into RevisionRecord afterall.
+               return self::getRevisionStore()->getTitle( $this->mRecord );
        }
 
        /**
@@ -839,7 +431,13 @@
         * @param Title $title
         */
        public function setTitle( $title ) {
-               $this->mTitle = $title;
+               $pageId = $this->getPage();
+               if ( $pageId !== null && $pageId > 0 && $pageId !== 
$title->getArticleID( ) ) {
+                       throw new InvalidArgumentException( 'Title mismatches 
the revision\'s page ID' );
+               }
+
+               // XXX: this really is not very pretty. Maybe move getTitle 
into RevisionRecord afterall.
+               $this->mRecord->setTransientData( 'title', $title );
        }
 
        /**
@@ -848,7 +446,7 @@
         * @return int|null
         */
        public function getPage() {
-               return $this->mPage;
+               return $this->mRecord->getPage();
        }
 
        /**
@@ -865,13 +463,7 @@
         * @return int
         */
        public function getUser( $audience = self::FOR_PUBLIC, User $user = 
null ) {
-               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;
-               }
+               return $this->mRecord->getUser( $audience, $user );
        }
 
        /**
@@ -899,23 +491,7 @@
         * @return string
         */
        public function getUserText( $audience = self::FOR_PUBLIC, User $user = 
null ) {
-               $this->loadMutableFields();
-
-               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;
-               }
+               return $this->mRecord->getUserText( $audience, $user );
        }
 
        /**
@@ -943,13 +519,7 @@
         * @return string
         */
        function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
-               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;
-               }
+               return $this->mRecord->getComment( $audience, $user );
        }
 
        /**
@@ -967,23 +537,15 @@
         * @return bool
         */
        public function isMinor() {
-               return (bool)$this->mMinorEdit;
+               return $this->mRecord->isMinor();
        }
 
        /**
         * @return int Rcid of the unpatrolled row, zero if there isn't one
         */
        public function isUnpatrolled() {
-               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;
+               // XXX: this really is not very pretty. Maybe move 
isUnpatrolled into RevisionRecord afterall.
+               return self::getRevisionStore()->isUnpatrolled( $this->mRecord 
);
        }
 
        /**
@@ -996,19 +558,8 @@
         * @return RecentChange|null
         */
        public function getRecentChange( $flags = 0 ) {
-               $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
-               );
+               // XXX: this really is not very pretty. Maybe move 
getRecentChange into RevisionRecord afterall.
+               return self::getRevisionStore()->getRecentChange( 
$this->mRecord, $flags );
        }
 
        /**
@@ -1017,14 +568,7 @@
         * @return bool
         */
        public function 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;
+               return $this->mRecord->isDeleted( $field );
        }
 
        /**
@@ -1033,9 +577,7 @@
         * @return int
         */
        public function getVisibility() {
-               $this->loadMutableFields();
-
-               return (int)$this->mDeleted;
+               return $this->mRecord->getVisibility();
        }
 
        /**
@@ -1053,54 +595,22 @@
         * @return Content|null
         */
        public function getContent( $audience = self::FOR_PUBLIC, User $user = 
null ) {
-               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();
-               }
+               return $this->mRecord->getContent( 'main', $audience, $user );
        }
 
        /**
         * Get original serialized data (without checking view restrictions)
         *
         * @since 1.21
+        * @deprecated since 1.30, use ContentStore::getBlob instead.
+        *
         * @return string
         */
        public function getSerializedData() {
-               if ( $this->mText === null ) {
-                       // Revision is immutable. Load on demand.
-                       $this->mText = $this->loadText();
-               }
-
-               return $this->mText;
-       }
-
-       /**
-        * 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;
+               // FIXME: there is no caching here. Could use 
setTransientData().
+               // XXX: if content is already loaded, we could serialize it.
+               $slot = $this->getMainSlot();
+               return self::getContentStore()->getBlob( $slot->getAddress() );
        }
 
        /**
@@ -1114,18 +624,7 @@
         *     see the CONTENT_MODEL_XXX constants.
         */
        public function getContentModel() {
-               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;
+               return $this->getMainSlot()->getModel();
        }
 
        /**
@@ -1134,18 +633,14 @@
         * 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() {
-               if ( !$this->mContentFormat ) {
-                       $handler = $this->getContentHandler();
-                       $this->mContentFormat = $handler->getDefaultFormat();
-
-                       assert( !empty( $this->mContentFormat ) );
-               }
-
-               return $this->mContentFormat;
+               // TODO: make sure we get a string name here, not an int id.
+               return $this->getMainSlot()->getStringField( 'cont_format' );
        }
 
        /**
@@ -1155,33 +650,21 @@
         * @return ContentHandler
         */
        public function getContentHandler() {
-               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 ContentHandler::getForModelID( $this->getContentModel() 
);
        }
 
        /**
         * @return string
         */
        public function getTimestamp() {
-               return wfTimestamp( TS_MW, $this->mTimestamp );
+               return $this->mRecord->getTimestamp();
        }
 
        /**
         * @return bool
         */
        public function isCurrent() {
-               return $this->mCurrent;
+               return $this->mRecord->isCurrent();
        }
 
        /**
@@ -1190,13 +673,7 @@
         * @return Revision|null
         */
        public function getPrevious() {
-               if ( $this->getTitle() ) {
-                       $prev = $this->getTitle()->getPreviousRevisionID( 
$this->getId() );
-                       if ( $prev ) {
-                               return self::newFromTitle( $this->getTitle(), 
$prev );
-                       }
-               }
-               return null;
+               return self::getRevisionStore()->getPreviousRevision( 
$this->mRecord );
        }
 
        /**
@@ -1205,38 +682,7 @@
         * @return Revision|null
         */
        public function getNext() {
-               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 );
+               return self::getRevisionStore()->getNextRevision( 
$this->mRecord );
        }
 
        /**
@@ -1269,35 +715,7 @@
                        return false;
                }
 
-               // 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;
-                       }
-
-                       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 );
+               return self::getContentStore()->expandBlob( $text, $flags, 
$row->old_id );
        }
 
        /**
@@ -1311,28 +729,7 @@
         * @return string
         */
        public static function compressRevisionText( &$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 );
+               return self::getContentStore()->compressRevisionData( $text );
        }
 
        /**
@@ -1343,46 +740,7 @@
         * @return string|bool Decompressed text, or false on failure
         */
        public static function decompressRevisionText( $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;
+               return self::getContentStore()->decompressRevisionData( $text, 
$flags );
        }
 
        /**
@@ -1394,181 +752,14 @@
         * @return int
         */
        public function insertOn( $dbw ) {
-               global $wgDefaultExternalStore, $wgContentHandlerUseDB;
-
                // 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;
 
-               // 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 {
-                               $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 ) {
-                       $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
-                       $dbw->insert( 'text',
-                               [
-                                       'old_id' => $old_id,
-                                       'old_text' => $data,
-                                       'old_flags' => $flags,
-                               ], __METHOD__
-                       );
-                       $this->mTextId = $dbw->insertId();
-               }
-
-               if ( $this->mComment === null ) {
-                       $this->mComment = "";
-               }
-
-               # Record the edit in revisions
-               $rev_id = $this->mId !== null
-                       ? $this->mId
-                       : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
-               $row = [
-                       'rev_id'         => $rev_id,
-                       'rev_page'       => $this->mPage,
-                       'rev_text_id'    => $this->mTextId,
-                       'rev_comment'    => $this->mComment,
-                       '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 ( $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 nextSequenceValue() was called
-                       $this->mId = $dbw->insertId();
-               }
-
-               // 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 )
-                       );
-               }
-
-               // Avoid PHP 7.1 warning of passing $this by reference
-               $revision = $this;
-               Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, 
$flags ] );
+               $this->mId = self::getRevisionStore()->insertRevisionOn( 
$this->mRecord, $dbw, $this->mQueryFlags );
 
                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" );
-                       }
-               }
-
-               $content = $this->getContent( self::RAW );
-               $prefixedDBkey = $title->getPrefixedDBkey();
-               $revId = $this->mId;
-
-               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"
-                       );
-               }
        }
 
        /**
@@ -1577,103 +768,7 @@
         * @return string
         */
        public static function 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;
+               return SlotRecord::base36Sha1( $text );
        }
 
        /**
@@ -1692,61 +787,12 @@
         * @return Revision|null Revision or null on error
         */
        public static function newNullRevision( $dbw, $pageId, $summary, 
$minor, $user = null ) {
-               global $wgContentHandlerUseDB, $wgContLang;
-
-               $fields = [ 'page_latest', 'page_namespace', 'page_title',
-                                               'rev_text_id', 'rev_len', 
'rev_sha1' ];
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'rev_content_model';
-                       $fields[] = 'rev_content_format';
+               if ( !$user ) {
+                       global $wgUser;
+                       $user = $wgUser;
                }
 
-               $current = $dbw->selectRow(
-                       [ 'page', 'revision' ],
-                       $fields,
-                       [
-                               'page_id' => $pageId,
-                               'page_latest=rev_id',
-                       ],
-                       __METHOD__,
-                       [ 'FOR UPDATE' ] // T51581
-               );
-
-               if ( $current ) {
-                       if ( !$user ) {
-                               global $wgUser;
-                               $user = $wgUser;
-                       }
-
-                       // Truncate for whole multibyte characters
-                       $summary = $wgContLang->truncate( $summary, 255 );
-
-                       $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;
+               return self::getRevisionStore()->newNullRevision( $dbw, 
$pageId, $summary, $minor, $user );
        }
 
        /**
@@ -1760,7 +806,12 @@
         * @return bool
         */
        public function userCan( $field, User $user = null ) {
-               return self::userCanBitfield( $this->getVisibility(), $field, 
$user );
+               if ( !$user ) {
+                       global $wgUser;
+                       $user = $wgUser;
+               }
+
+               return $this->mRecord->userCan( $field, $user ); // FIXME: not 
in the interface...
        }
 
        /**
@@ -1780,35 +831,13 @@
        public static function userCanBitfield( $bitfield, $field, User $user = 
null,
                Title $title = null
        ) {
-               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;
+               if ( !$user ) {
+                       global $wgUser;
+                       $user = $wgUser;
                }
+
+               // FIXME: move userCanBitfield into RevisionStore
+               return self::getRevisionStore()->userCanBitfield( $field, $user 
);
        }
 
        /**
@@ -1820,18 +849,7 @@
         * @return string|bool False if not found
         */
        static function getTimestampFromId( $title, $id, $flags = 0 ) {
-               $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;
+               return self::getRevisionStore()->getTimestampFromId( $title, 
$id, $flags );
        }
 
        /**
@@ -1842,12 +860,7 @@
         * @return int
         */
        static function countByPageId( $db, $id ) {
-               $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
-                       [ 'rev_page' => $id ], __METHOD__ );
-               if ( $row ) {
-                       return $row->revCount;
-               }
-               return 0;
+               return self::getRevisionStore()->countByPageId( $db, $id );
        }
 
        /**
@@ -1858,11 +871,7 @@
         * @return int
         */
        static function countByTitle( $db, $title ) {
-               $id = $title->getArticleID();
-               if ( $id ) {
-                       return self::countByPageId( $db, $id );
-               }
-               return 0;
+               return self::getRevisionStore()->countByTitle( $db, $title );
        }
 
        /**
@@ -1882,28 +891,7 @@
         * @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 );
-               }
-
-               $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;
+               return self::getRevisionStore()->userWasLastToEdit( $db, 
$pageId, $userId, $since );
        }
 
        /**
@@ -1920,45 +908,6 @@
         * @since 1.28
         */
        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->getWikiID(), 
$pageId, $revId ),
-                       $cache::TTL_WEEK,
-                       function ( $curValue, &$ttl, array &$setOpts ) use ( 
$db, $pageId, $revId ) {
-                               $setOpts += Database::getCacheSetOptions( $db );
-
-                               $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;
-               }
+               return self::getRevisionStore()->newKnownCurrent( $db, $pageId, 
$revId );
        }
 }
diff --git a/includes/Storage/DefaultRevisionRecord.php 
b/includes/Storage/DefaultRevisionRecord.php
index 4f26311..1034f96 100644
--- a/includes/Storage/DefaultRevisionRecord.php
+++ b/includes/Storage/DefaultRevisionRecord.php
@@ -33,6 +33,9 @@
 /**
  * Lazy loading representation of a page revision.
  *
+ * @todo RevisionRecord is probably not needed as a separate interface.
+ * It's useful for now to be explicit about what should be exposed, and what 
shouldn't.
+ *
  * Callbacks are used for lazy loading, so this class
  * has no knowledge of the actual storage mechanism.
  */
@@ -461,7 +464,8 @@
         * @param User|null $user User object to check, or null to use $wgUser
         * @return bool
         */
-       protected function userCan( $field, User $user = null ) {
+       public function userCan( $field, User $user = null ) {
+               // FIXME: require user!
                return self::userCanBitfield( $this->getVisibility(), $field, 
$user );
        }
 
@@ -472,7 +476,7 @@
         *
         * MCR migration note: this replaces Revision::userCanBitfield
         *
-        * @todo: FIXME: move all checks that depend on the user's identity 
into RevisionStore!
+        * @todo: FIXME: move this and all checks that depend on the user's 
identity into RevisionStore!
         *
         * @param int $bitfield Current field
         * @param int $field One of self::DELETED_TEXT = File::DELETED_FILE,
@@ -486,6 +490,8 @@
        public static function userCanBitfield( $bitfield, $field, User $user = 
null,
                Title $title = null
        ) {
+               // FIXME: require user!
+
                if ( $bitfield & $field ) { // aspect is deleted
                        if ( $user === null ) {
                                global $wgUser; // FIXME!
diff --git a/includes/Storage/RevisionLookup.php 
b/includes/Storage/RevisionLookup.php
index 9ff244c..1ed5f64 100644
--- a/includes/Storage/RevisionLookup.php
+++ b/includes/Storage/RevisionLookup.php
@@ -76,21 +76,22 @@
         *
         * MCR migration note: this replaces Revision::getPrevious
         *
-        * @param Title $title
-        * @param $revId
+        * @param RevisionRecord $rev
         *
         * @return RevisionRecord|null
         */
-       public function getPreviousRevision( Title $title, $revId );
+       public function getPreviousRevision( RevisionRecord $rev );
 
        /**
         * Get next revision for this title
         *
         * MCR migration note: this replaces Revision::getNext
         *
+        * @param RevisionRecord $rev
+        *
         * @return RevisionRecord|null
         */
-       public function getNextRevision( Title $title, $revId );
+       public function getNextRevision( RevisionRecord $rev );
 
        /**
         * Load a revision based on a known page ID and current revision ID 
from the DB
diff --git a/includes/Storage/RevisionStore.php 
b/includes/Storage/RevisionStore.php
index 3d49847..2e0f934 100644
--- a/includes/Storage/RevisionStore.php
+++ b/includes/Storage/RevisionStore.php
@@ -385,10 +385,10 @@
         * @param int $pageId ID number of the page to read from
         * @param string $summary RevisionRecord's summary
         * @param bool $minor Whether the revision should be considered as minor
-        * @param User|null $user User object to use or null for $wgUser
+        * @param User $user User object to use or null for $wgUser
         * @return RevisionRecord|null RevisionRecord or null on error
         */
-       public function newNullRevision( IDatabase $dbw, $pageId, $summary, 
$minor, $user = null ) {
+       public function newNullRevision( IDatabase $dbw, $pageId, $summary, 
$minor, User $user ) {
                global $wgContentHandlerUseDB, $wgContLang;
 
                $this->checkDatabaseWikiId( $dbw );
@@ -413,11 +413,6 @@
                );
 
                if ( $current ) {
-                       if ( !$user ) {
-                               global $wgUser;
-                               $user = $wgUser;
-                       }
-
                        // Truncate for whole multibyte characters
                        $summary = $wgContLang->truncate( $summary, 255 );
 
@@ -966,32 +961,6 @@
        }
 
        /**
-        * Return a wrapper for a series of database rows to
-        * fetch all of a given page's revisions in turn.
-        * Each row can be fed to the constructor to get objects.
-        *
-        * MCR migration note: this replaces Revision::fetchRevision
-        *
-        * @param LinkTarget $title
-        * @return ResultWrapper
-        * @deprecated Since 1.28
-        */
-       public function fetchRevisionRows( LinkTarget $title ) {
-               $db = $this->getDBConnection( DB_REPLICA );
-               $row = $this->fetchRevisionRowFromConds(
-                       $db,
-                       [
-                               'rev_id=page_latest',
-                               'page_namespace' => $title->getNamespace(),
-                               'page_title' => $title->getDBkey()
-                       ]
-               );
-
-               $this->releaseDBConnection( $db );
-               return new FakeResultWrapper( $row ? [ $row ] : [] );
-       }
-
-       /**
         * Throws an exception if the given database connection does not belong 
to the wiki this
         * RevisionStore is bound to.
         *
@@ -1210,13 +1179,13 @@
         *
         * MCR migration note: this replaces Revision::getPrevious
         *
-        * @param Title $title
-        * @param $revId
+        * @param RevisionRecord $rev
         *
         * @return RevisionRecord|null
         */
-       public function getPreviousRevision( Title $title, $revId ) {
-               $prev = $title->getPreviousRevisionID( $revId );
+       public function getPreviousRevision( RevisionRecord $rev ) {
+               $title = $this->getTitle( $rev );
+               $prev = $title->getPreviousRevisionID( $rev->getId() );
                if ( $prev ) {
                        return $this->getRevisionByTitle( $title, $prev );
                }
@@ -1228,10 +1197,13 @@
         *
         * MCR migration note: this replaces Revision::getNext
         *
+        * @param RevisionRecord $rev
+        *
         * @return RevisionRecord|null
         */
-       public function getNextRevision( Title $title, $revId ) {
-               $next = $title->getNextRevisionID( $revId );
+       public function getNextRevision( RevisionRecord $rev ) {
+               $title = $this->getTitle( $rev );
+               $next = $title->getNextRevisionID( $rev->getId() );
                if ( $next ) {
                        return $this->getRevisionByTitle( $title, $next );
                }

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I4f24e7fbb683cb51f3fd8b250732bae9c7541ba2
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: Aaron Schulz <asch...@wikimedia.org>
Gerrit-Reviewer: Anomie <bjor...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Cicalese <cin...@gmail.com>
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