'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 probably came from self::selectArchiveFields()
+ // 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,
$row = self::fetchFromConds( $db, $conditions, $flags );
if ( $row ) {
$rev = new Revision( $row );
- $rev->mWiki = $db->getWikiID();
+ $rev->mWiki = $db->getDomainID();
return $rev;
}
* @return stdClass
*/
private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
- $fields = array_merge(
- self::selectFields(),
- self::selectPageFields(),
- self::selectUserFields()
- );
+ $revQuery = self::getQueryInfo( [ 'page', 'user' ] );
$options = [];
if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
$options[] = 'FOR UPDATE';
}
return $db->selectRow(
- [ 'revision', 'page', 'user' ],
- $fields,
+ $revQuery['tables'],
+ $revQuery['fields'],
$conditions,
__METHOD__,
$options,
- [ 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() ]
+ $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 self::getQueryInfo( [ 'user' ] ) instead.
* @return array
*/
public static function userJoinCond() {
+ wfDeprecated( __METHOD__, '1.31' );
return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
}
* 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 self::getQueryInfo( [ 'page' ] ) instead.
* @return array
*/
public static function pageJoinCond() {
+ wfDeprecated( __METHOD__, '1.31' );
return [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
}
/**
* Return the list of revision fields that should be selected to create
* a new revision.
- * @todo Deprecate this in favor of a method that returns tables and joins
- * as well, and use CommentStore::getJoin().
+ * @deprecated since 1.31, use self::getQueryInfo() instead.
* @return array
*/
public static function selectFields() {
global $wgContentHandlerUseDB;
+ wfDeprecated( __METHOD__, '1.31' );
+
$fields = [
'rev_id',
'rev_page',
/**
* Return the list of revision fields that should be selected to create
* a new revision from an archive row.
- * @todo Deprecate this in favor of a method that returns tables and joins
- * as well, and use CommentStore::getJoin().
+ * @deprecated since 1.31, use self::getArchiveQueryInfo() instead.
* @return array
*/
public static function selectArchiveFields() {
global $wgContentHandlerUseDB;
+
+ wfDeprecated( __METHOD__, '1.31' );
+
$fields = [
'ar_id',
'ar_page_id',
/**
* Return the list of text fields that should be selected to read the
* revision text
+ * @deprecated since 1.31, use self::getQueryInfo( [ 'text' ] ) instead.
* @return array
*/
public static function selectTextFields() {
+ wfDeprecated( __METHOD__, '1.31' );
return [
'old_text',
'old_flags'
/**
* Return the list of page fields that should be selected from page table
+ * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead.
* @return array
*/
public static function selectPageFields() {
+ wfDeprecated( __METHOD__, '1.31' );
return [
'page_namespace',
'page_title',
/**
* Return the list of user fields that should be selected from user table
+ * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead.
* @return array
*/
public static function selectUserFields() {
+ wfDeprecated( __METHOD__, '1.31' );
return [ 'user_name' ];
}
+ /**
+ * Return the tables, fields, and join conditions to be selected to create
+ * a new revision object.
+ * @since 1.31
+ * @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
+ * - 'text': Join with the text table, and select fields to load page text
+ * @return array With three keys:
+ * - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+ * - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
+ * - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+ */
+ public static function getQueryInfo( $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
+ * @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() {
+ 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;
+ }
+
/**
* Do a batched query to get the parent revision lengths
* @param IDatabase $db
* @throws MWException
* @access private
*/
- function __construct( $row ) {
+ public 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 = CommentStore::newKey( 'rev_comment' )
- // Legacy because $row probably came 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;
- }
+ $this->constructFromDbRowObject( $row );
+ } elseif ( is_array( $row ) ) {
+ $this->constructFromRowArray( $row );
+ } else {
+ throw new MWException( 'Revision constructor passed invalid row format.' );
+ }
+ $this->mUnpatrolled = null;
+ }
- 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;
- }
+ /**
+ * @param object $row
+ */
+ 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_content_model ) ) {
- $this->mContentModel = null; # determine on demand if needed
- } else {
- $this->mContentModel = strval( $row->rev_content_model );
- }
+ if ( !isset( $row->rev_len ) ) {
+ $this->mSize = null;
+ } else {
+ $this->mSize = intval( $row->rev_len );
+ }
- if ( !isset( $row->rev_content_format ) ) {
- $this->mContentFormat = null; # determine on demand if needed
- } else {
- $this->mContentFormat = strval( $row->rev_content_format );
- }
+ if ( !isset( $row->rev_sha1 ) ) {
+ $this->mSha1 = null;
+ } else {
+ $this->mSha1 = $row->rev_sha1;
+ }
- // 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;
- }
+ 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;
+ }
- // 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;
- } 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" );
- }
+ if ( !isset( $row->rev_content_model ) ) {
+ $this->mContentModel = null; # determine on demand if needed
+ } else {
+ $this->mContentModel = strval( $row->rev_content_model );
+ }
- $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?
- }
+ if ( !isset( $row->rev_content_format ) ) {
+ $this->mContentFormat = null; # determine on demand if needed
+ } else {
+ $this->mContentFormat = strval( $row->rev_content_format );
+ }
- $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;
+ // 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;
+ }
- $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'];
+ // 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;
+ }
- $this->mContentModel = $this->mContent->getModel();
- $this->mContentHandler = null;
+ /**
+ * @param array $row
+ *
+ * @throws MWException
+ */
+ private function constructFromRowArray( array $row ) {
+ // Build a new revision to be saved...
+ global $wgUser; // ugh
- $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 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.' );
}
- // 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." );
- }
+ // @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" );
}
- $this->mCurrent = false;
+ $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'];
- // 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();
- }
+ $this->mContentModel = $this->mContent->getModel();
+ $this->mContentHandler = null;
- // Same for sha1
- if ( $this->mSha1 === null ) {
- $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
+ $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." );
}
+ }
- // force lazy init
- $this->getContentModel();
- $this->getContentFormat();
- } else {
- throw new MWException( 'Revision constructor passed invalid row format.' );
+ $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();
}
- $this->mUnpatrolled = null;
+
+ // 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 should only be used for proposed revisions that turn out to be null edits
*
* @since 1.28
+ * @deprecated since 1.31, please reuse old Revision object
* @param int $id User ID
* @param string $name User name
*/
// 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(
- [ 'page', 'revision' ],
- self::selectPageFields(),
- [ 'page_id=rev_page', 'rev_id' => $this->mId ],
- __METHOD__
+ [ '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
$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->makeGlobalKey( 'revision', $db->getDomainID(), $pageId, $revId ),
$cache::TTL_WEEK,
function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
$setOpts += Database::getCacheSetOptions( $db );