* @param object $row
* @param int $queryFlags
* @param Title|null $title
- *
+ * @param bool $fromCache if true, the returned RevisionRecord will ensure that no stale
+ * data is returned from getters, by querying the database as needed
* @return RevisionRecord
*/
- public function newRevisionFromRow( $row, $queryFlags = 0, Title $title = null ) {
+ public function newRevisionFromRow(
+ $row,
+ $queryFlags = 0,
+ Title $title = null,
+ $fromCache = false
+ ) {
Assert::parameterType( 'object', $row, '$row' );
if ( !$title ) {
$slots = $this->newRevisionSlots( $row->rev_id, $row, $queryFlags, $title );
- return new RevisionStoreRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
+ // If this is a cached row, instantiate a cache-aware revision class to avoid stale data.
+ if ( $fromCache ) {
+ $rev = new RevisionStoreCacheRecord(
+ function ( $revId ) use ( $queryFlags ) {
+ $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
+ return $this->fetchRevisionRowFromConds(
+ $db,
+ [ 'rev_id' => intval( $revId ) ]
+ );
+ },
+ $title, $user, $comment, $row, $slots, $this->wikiId
+ );
+ } else {
+ $rev = new RevisionStoreRecord(
+ $title, $user, $comment, $row, $slots, $this->wikiId );
+ }
+ return $rev;
}
/**
'page_is_redirect',
'page_len',
] );
- $ret['joins']['page'] = [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
+ $ret['joins']['page'] = [ 'JOIN', [ 'page_id = rev_page' ] ];
}
if ( in_array( 'user', $options, true ) ) {
'old_text',
'old_flags'
] );
- $ret['joins']['text'] = [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ];
+ $ret['joins']['text'] = [ 'JOIN', [ 'rev_text_id=old_id' ] ];
}
return $ret;
'content_address',
'content_model',
] );
- $ret['joins']['content'] = [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ];
+ $ret['joins']['content'] = [ 'JOIN', [ 'slot_content_id = content_id' ] ];
if ( in_array( 'model', $options, true ) ) {
// Use left join to attach model name, so we still find the revision row even
}
/**
- * Get previous revision for this title
+ * Get the revision before $rev in the page's history, if any.
+ * Will return null for the first revision but also for deleted or unsaved revisions.
*
* MCR migration note: this replaces Revision::getPrevious
*
+ * @see Title::getPreviousRevisionID
+ * @see PageArchive::getPreviousRevision
+ *
* @param RevisionRecord $rev
* @param Title|null $title if known (optional)
*
* @return RevisionRecord|null
*/
public function getPreviousRevision( RevisionRecord $rev, Title $title = null ) {
+ if ( !$rev->getId() || !$rev->getPageId() ) {
+ // revision is unsaved or otherwise incomplete
+ return null;
+ }
+
+ if ( $rev instanceof RevisionArchiveRecord ) {
+ // revision is deleted, so it's not part of the page history
+ return null;
+ }
+
if ( $title === null ) {
+ // this would fail for deleted revisions
$title = $this->getTitle( $rev->getPageId(), $rev->getId() );
}
+
$prev = $title->getPreviousRevisionID( $rev->getId() );
- if ( $prev ) {
- return $this->getRevisionByTitle( $title, $prev );
+ if ( !$prev ) {
+ return null;
}
- return null;
+
+ return $this->getRevisionByTitle( $title, $prev );
}
/**
- * Get next revision for this title
+ * Get the revision after $rev in the page's history, if any.
+ * Will return null for the latest revision but also for deleted or unsaved revisions.
*
* MCR migration note: this replaces Revision::getNext
*
+ * @see Title::getNextRevisionID
+ *
* @param RevisionRecord $rev
* @param Title|null $title if known (optional)
*
* @return RevisionRecord|null
*/
public function getNextRevision( RevisionRecord $rev, Title $title = null ) {
+ if ( !$rev->getId() || !$rev->getPageId() ) {
+ // revision is unsaved or otherwise incomplete
+ return null;
+ }
+
+ if ( $rev instanceof RevisionArchiveRecord ) {
+ // revision is deleted, so it's not part of the page history
+ return null;
+ }
+
if ( $title === null ) {
+ // this would fail for deleted revisions
$title = $this->getTitle( $rev->getPageId(), $rev->getId() );
}
+
$next = $title->getNextRevisionID( $rev->getId() );
- if ( $next ) {
- return $this->getRevisionByTitle( $title, $next );
+ if ( !$next ) {
+ return null;
}
- return null;
+
+ return $this->getRevisionByTitle( $title, $next );
}
/**
return false;
}
+ // Load the row from cache if possible. If not possible, populate the cache.
+ // As a minor optimization, remember if this was a cache hit or miss.
+ // We can sometimes avoid a database query later if this is a cache miss.
+ $fromCache = true;
$row = $this->cache->getWithSetCallback(
// Page/rev IDs passed in from DB to reflect history merges
$this->getRevisionRowCacheKey( $db, $pageId, $revId ),
WANObjectCache::TTL_WEEK,
- function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
+ function ( $curValue, &$ttl, array &$setOpts ) use (
+ $db, $pageId, $revId, &$fromCache
+ ) {
$setOpts += Database::getCacheSetOptions( $db );
-
- $conds = [
- 'rev_page' => intval( $pageId ),
- 'page_id' => intval( $pageId ),
- 'rev_id' => intval( $revId ),
- ];
-
- $row = $this->fetchRevisionRowFromConds( $db, $conds );
- return $row ?: false; // don't cache negatives
+ $row = $this->fetchRevisionRowFromConds( $db, [ 'rev_id' => intval( $revId ) ] );
+ if ( $row ) {
+ $fromCache = false;
+ }
+ return $row; // don't cache negatives
}
);
- // Reflect revision deletion and user renames
+ // Reflect revision deletion and user renames.
if ( $row ) {
- return $this->newRevisionFromRow( $row, 0, $title );
+ return $this->newRevisionFromRow( $row, 0, $title, $fromCache );
} else {
return false;
}