use Wikimedia\Rdbms\DBConnRef;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\IResultWrapper;
/**
* Service for looking up page revisions.
/**
* @var bool|string
*/
- private $wikiId;
+ private $dbDomain;
/**
* @var boolean
* @param ILoadBalancer $loadBalancer
* @param SqlBlobStore $blobStore
* @param WANObjectCache $cache A cache for caching revision rows. This can be the local
- * wiki's default instance even if $wikiId refers to a different wiki, since
+ * wiki's default instance even if $dbDomain refers to a different wiki, since
* makeGlobalKey() is used to constructed a key that allows cached revision rows from
* the same database to be re-used between wikis. For example, enwiki and frwiki will
* use the same cache keys for revision rows from the wikidatawiki database, regardless
* @param SlotRoleRegistry $slotRoleRegistry
* @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
* @param ActorMigration $actorMigration
- * @param bool|string $wikiId
- *
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
*/
public function __construct(
ILoadBalancer $loadBalancer,
SlotRoleRegistry $slotRoleRegistry,
$mcrMigrationStage,
ActorMigration $actorMigration,
- $wikiId = false
+ $dbDomain = false
) {
- Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+ Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
Assert::parameterType( 'integer', $mcrMigrationStage, '$mcrMigrationStage' );
Assert::parameter(
( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== SCHEMA_COMPAT_READ_BOTH,
$this->slotRoleRegistry = $slotRoleRegistry;
$this->mcrMigrationStage = $mcrMigrationStage;
$this->actorMigration = $actorMigration;
- $this->wikiId = $wikiId;
+ $this->dbDomain = $dbDomain;
$this->logger = new NullLogger();
}
* @throws RevisionAccessException
*/
private function assertCrossWikiContentLoadingIsSafe() {
- if ( $this->wikiId !== false && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ if ( $this->dbDomain !== false && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
throw new RevisionAccessException(
"Cross-wiki content loading is not supported by the pre-MCR schema"
);
*/
private function getDBConnection( $mode, $groups = [] ) {
$lb = $this->getDBLoadBalancer();
- return $lb->getConnection( $mode, $groups, $this->wikiId );
+ return $lb->getConnection( $mode, $groups, $this->dbDomain );
}
/**
*/
private function getDBConnectionRef( $mode ) {
$lb = $this->getDBLoadBalancer();
- return $lb->getConnectionRef( $mode, [], $this->wikiId );
+ return $lb->getConnectionRef( $mode, [], $this->dbDomain );
}
/**
$queryFlags = self::READ_NORMAL;
}
- $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->wikiId === false );
+ $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->dbDomain === false );
list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
$titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
$comment,
(object)$revisionRow,
new RevisionSlots( $newSlots ),
- $this->wikiId
+ $this->dbDomain
);
return $rev;
throw new MWException( 'Failed to get database lock for T202032' );
}
$fname = __METHOD__;
- $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) {
- $dbw->unlock( 'fix-for-T202032', $fname );
- } );
+ $dbw->onTransactionResolution(
+ function ( $trigger, IDatabase $dbw ) use ( $fname ) {
+ $dbw->unlock( 'fix-for-T202032', $fname );
+ }
+ );
$dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ );
/**
* @param int $revId The revision to load slots for.
* @param int $queryFlags
+ * @param Title $title
*
* @return SlotRecord[]
*/
- private function loadSlotRecords( $revId, $queryFlags ) {
+ private function loadSlotRecords( $revId, $queryFlags, Title $title ) {
$revQuery = self::getSlotsQueryInfo( [ 'content' ] );
list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
$revQuery['joins']
);
+ $slots = $this->constructSlotRecords( $revId, $res, $queryFlags, $title );
+
+ return $slots;
+ }
+
+ /**
+ * Factory method for SlotRecords based on known slot rows.
+ *
+ * @param int $revId The revision to load slots for.
+ * @param object[]|IResultWrapper $slotRows
+ * @param int $queryFlags
+ * @param Title $title
+ *
+ * @return SlotRecord[]
+ */
+ private function constructSlotRecords( $revId, $slotRows, $queryFlags, Title $title ) {
$slots = [];
- foreach ( $res as $row ) {
- // resolve role names and model names from in-memory cache, instead of joining.
- $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id );
- $row->model_name = $this->contentModelStore->getName( (int)$row->content_model );
+ foreach ( $slotRows as $row ) {
+ // Resolve role names and model names from in-memory cache, if they were not joined in.
+ if ( !isset( $row->role_name ) ) {
+ $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id );
+ }
+
+ if ( !isset( $row->model_name ) ) {
+ if ( isset( $row->content_model ) ) {
+ $row->model_name = $this->contentModelStore->getName( (int)$row->content_model );
+ } else {
+ // We may get here if $row->model_name is set but null, perhaps because it
+ // came from rev_content_model, which is NULL for the default model.
+ $slotRoleHandler = $this->slotRoleRegistry->getRoleHandler( $row->role_name );
+ $row->model_name = $slotRoleHandler->getDefaultModel( $title );
+ }
+ }
+
+ if ( !isset( $row->content_id ) && isset( $row->rev_text_id ) ) {
+ $row->slot_content_id
+ = $this->emulateContentId( intval( $row->rev_text_id ) );
+ }
$contentCallback = function ( SlotRecord $slot ) use ( $queryFlags ) {
return $this->loadSlotContent( $slot, null, null, null, $queryFlags );
throw new RevisionAccessException(
'Main slot of revision ' . $revId . ' not found in database!'
);
- };
+ }
return $slots;
}
/**
- * Factory method for RevisionSlots.
+ * Factory method for RevisionSlots based on a revision ID.
*
* @note If other code has a need to construct RevisionSlots objects, this should be made
* public, since RevisionSlots instances should not be constructed directly.
*
* @param int $revId
* @param object $revisionRow
+ * @param object[]|null $slotRows
* @param int $queryFlags
* @param Title $title
*
private function newRevisionSlots(
$revId,
$revisionRow,
+ $slotRows,
$queryFlags,
Title $title
) {
- if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
+ if ( $slotRows ) {
+ $slots = new RevisionSlots(
+ $this->constructSlotRecords( $revId, $slotRows, $queryFlags, $title )
+ );
+ } elseif ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
$mainSlot = $this->emulateMainSlot_1_29( $revisionRow, $queryFlags, $title );
// @phan-suppress-next-line PhanTypeInvalidCallableArraySize false positive
$slots = new RevisionSlots( [ SlotRecord::MAIN => $mainSlot ] );
// XXX: do we need the same kind of caching here
// that getKnownCurrentRevision uses (if $revId == page_latest?)
- $slots = new RevisionSlots( function () use( $revId, $queryFlags ) {
- return $this->loadSlotRecords( $revId, $queryFlags );
+ $slots = new RevisionSlots( function () use( $revId, $queryFlags, $title ) {
+ return $this->loadSlotRecords( $revId, $queryFlags, $title );
} );
}
$row->ar_user ?? null,
$row->ar_user_text ?? null,
$row->ar_actor ?? null,
- $this->wikiId
+ $this->dbDomain
);
} catch ( InvalidArgumentException $ex ) {
wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() );
// Legacy because $row may have come from self::selectFields()
$comment = $this->commentStore->getCommentLegacy( $db, 'ar_comment', $row, true );
- $slots = $this->newRevisionSlots( $row->ar_rev_id, $row, $queryFlags, $title );
+ $slots = $this->newRevisionSlots( $row->ar_rev_id, $row, null, $queryFlags, $title );
- return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
+ return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->dbDomain );
}
/**
*
* MCR migration note: this replaces Revision::newFromRow
*
- * @param object $row
+ * @param object $row A database row generated from a query based on getQueryInfo()
* @param int $queryFlags
* @param Title|null $title
* @param bool $fromCache if true, the returned RevisionRecord will ensure that no stale
$queryFlags = 0,
Title $title = null,
$fromCache = false
+ ) {
+ return $this->newRevisionFromRowAndSlots( $row, null, $queryFlags, $title, $fromCache );
+ }
+
+ /**
+ * @param object $row A database row generated from a query based on getQueryInfo()
+ * @param null|object[] $slotRows Database rows generated from a query based on
+ * getSlotsQueryInfo with the 'content' flag set.
+ * @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
+ * @throws MWException
+ * @see RevisionFactory::newRevisionFromRow
+ *
+ * MCR migration note: this replaces Revision::newFromRow
+ *
+ */
+ public function newRevisionFromRowAndSlots(
+ $row,
+ $slotRows,
+ $queryFlags = 0,
+ Title $title = null,
+ $fromCache = false
) {
Assert::parameterType( 'object', $row, '$row' );
$row->rev_user ?? null,
$row->rev_user_text ?? null,
$row->rev_actor ?? null,
- $this->wikiId
+ $this->dbDomain
);
} catch ( InvalidArgumentException $ex ) {
wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() );
// Legacy because $row may have come from self::selectFields()
$comment = $this->commentStore->getCommentLegacy( $db, 'rev_comment', $row, true );
- $slots = $this->newRevisionSlots( $row->rev_id, $row, $queryFlags, $title );
+ $slots = $this->newRevisionSlots( $row->rev_id, $row, $slotRows, $queryFlags, $title );
// If this is a cached row, instantiate a cache-aware revision class to avoid stale data.
if ( $fromCache ) {
[ 'rev_id' => intval( $revId ) ]
);
},
- $title, $user, $comment, $row, $slots, $this->wikiId
+ $title, $user, $comment, $row, $slots, $this->dbDomain
);
} else {
$rev = new RevisionStoreRecord(
- $title, $user, $comment, $row, $slots, $this->wikiId );
+ $title, $user, $comment, $row, $slots, $this->dbDomain );
}
return $rev;
}
}
}
- $revision = new MutableRevisionRecord( $title, $this->wikiId );
+ $revision = new MutableRevisionRecord( $title, $this->dbDomain );
$this->initializeMutableRevisionFromArray( $revision, $fields );
if ( isset( $fields['content'] ) && is_array( $fields['content'] ) ) {
// remote wiki with unsuppressed ids, due to issues described in T222212.
if ( isset( $fields['user'] ) &&
( $fields['user'] instanceof UserIdentity ) &&
- ( $this->wikiId === false ||
+ ( $this->dbDomain === false ||
( !$fields['user']->getId() && !$fields['user']->getActorId() ) )
) {
$user = $fields['user'];
$fields['user'] ?? null,
$fields['user_text'] ?? null,
$fields['actor'] ?? null,
- $this->wikiId
+ $this->dbDomain
);
} catch ( InvalidArgumentException $ex ) {
$user = null;
// within web requests to certain avoid bugs like T93866 and T94407.
if ( !$rev
&& !( $flags & self::READ_LATEST )
- && $lb->getServerCount() > 1
+ && $lb->hasStreamingReplicaServers()
&& $lb->hasOrMadeRecentMasterChanges()
) {
$flags = self::READ_LATEST;
* @throws MWException
*/
private function checkDatabaseWikiId( IDatabase $db ) {
- $storeWiki = $this->wikiId;
+ $storeWiki = $this->dbDomain;
$dbWiki = $db->getDomainID();
if ( $dbWiki === $storeWiki ) {
if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$db = $this->getDBConnectionRef( DB_REPLICA );
- $ret['tables']['slots'] = 'revision';
+ $ret['tables'][] = 'revision';
- $ret['fields']['slot_revision_id'] = 'slots.rev_id';
+ $ret['fields']['slot_revision_id'] = 'rev_id';
$ret['fields']['slot_content_id'] = 'NULL';
- $ret['fields']['slot_origin'] = 'slots.rev_id';
+ $ret['fields']['slot_origin'] = 'rev_id';
$ret['fields']['role_name'] = $db->addQuotes( SlotRecord::MAIN );
if ( in_array( 'content', $options, true ) ) {
- $ret['fields']['content_size'] = 'slots.rev_len';
- $ret['fields']['content_sha1'] = 'slots.rev_sha1';
+ $ret['fields']['content_size'] = 'rev_len';
+ $ret['fields']['content_sha1'] = 'rev_sha1';
$ret['fields']['content_address']
- = $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] );
+ = $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'rev_text_id' ] );
+
+ // Allow the content_id field to be emulated later
+ $ret['fields']['rev_text_id'] = 'rev_text_id';
if ( $this->contentHandlerUseDB ) {
- $ret['fields']['model_name'] = 'slots.rev_content_model';
+ $ret['fields']['model_name'] = 'rev_content_model';
} else {
$ret['fields']['model_name'] = 'NULL';
}