X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FStorage%2FRevisionStore.php;h=584142bfc1a9bf501513adf15c0b7057d4925f8b;hb=242a166d68c55e72d8bfe3f99872e80d64b2c30e;hp=e7c90604115434af24e3422151610f1a9a09c7d0;hpb=db8f62e57d624426953c88925f56c098e986faa0;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Storage/RevisionStore.php b/includes/Storage/RevisionStore.php index e7c9060411..584142bfc1 100644 --- a/includes/Storage/RevisionStore.php +++ b/includes/Storage/RevisionStore.php @@ -26,6 +26,7 @@ namespace MediaWiki\Storage; +use ActorMigration; use CommentStore; use CommentStoreComment; use Content; @@ -97,6 +98,11 @@ class RevisionStore */ private $commentStore; + /** + * @var ActorMigration + */ + private $actorMigration; + /** * @var LoggerInterface */ @@ -109,6 +115,7 @@ class RevisionStore * @param SqlBlobStore $blobStore * @param WANObjectCache $cache * @param CommentStore $commentStore + * @param ActorMigration $actorMigration * @param bool|string $wikiId */ public function __construct( @@ -116,6 +123,7 @@ class RevisionStore SqlBlobStore $blobStore, WANObjectCache $cache, CommentStore $commentStore, + ActorMigration $actorMigration, $wikiId = false ) { Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' ); @@ -124,6 +132,7 @@ class RevisionStore $this->blobStore = $blobStore; $this->cache = $cache; $this->commentStore = $commentStore; + $this->actorMigration = $actorMigration; $this->wikiId = $wikiId; $this->logger = new NullLogger(); } @@ -132,6 +141,13 @@ class RevisionStore $this->logger = $logger; } + /** + * @return bool Whether the store is read-only + */ + public function isReadOnly() { + return $this->blobStore->isReadOnly(); + } + /** * @return bool */ @@ -381,14 +397,16 @@ class RevisionStore $user = $this->failOnNull( $rev->getUser( RevisionRecord::RAW ), 'user' ); $timestamp = $this->failOnEmpty( $rev->getTimestamp(), 'timestamp field' ); + // Checks. + $this->failOnNull( $user->getId(), 'user field' ); + $this->failOnEmpty( $user->getName(), 'user_text field' ); + # Record the edit in revisions $row = [ 'rev_page' => $pageId, 'rev_parent_id' => $parentId, 'rev_text_id' => $textId, 'rev_minor_edit' => $rev->isMinor() ? 1 : 0, - 'rev_user' => $this->failOnNull( $user->getId(), 'user field' ), - 'rev_user_text' => $this->failOnEmpty( $user->getName(), 'user_text field' ), 'rev_timestamp' => $dbw->timestamp( $timestamp ), 'rev_deleted' => $rev->getVisibility(), 'rev_len' => $size, @@ -404,6 +422,10 @@ class RevisionStore $this->commentStore->insertWithTempTable( $dbw, 'rev_comment', $comment ); $row += $commentFields; + list( $actorFields, $actorCallback ) = + $this->actorMigration->getInsertValuesWithTempTable( $dbw, 'rev_user', $user ); + $row += $actorFields; + if ( $this->contentHandlerUseDB ) { // MCR migration note: rev_content_model and rev_content_format will go away @@ -421,22 +443,21 @@ class RevisionStore $row['rev_id'] = intval( $dbw->insertId() ); } $commentCallback( $row['rev_id'] ); + $actorCallback( $row['rev_id'], $row ); // Insert IP revision into ip_changes for use when querying for a range. - if ( $row['rev_user'] === 0 && IP::isValid( $row['rev_user_text'] ) ) { + if ( $user->getId() === 0 && IP::isValid( $user->getName() ) ) { $ipcRow = [ 'ipc_rev_id' => $row['rev_id'], 'ipc_rev_timestamp' => $row['rev_timestamp'], - 'ipc_hex' => IP::toHex( $row['rev_user_text'] ), + 'ipc_hex' => IP::toHex( $user->getName() ), ]; $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ ); } - $newSlot = SlotRecord::newSaved( $row['rev_id'], $blobAddress, $slot ); + $newSlot = SlotRecord::newSaved( $row['rev_id'], $textId, $blobAddress, $slot ); $slots = new RevisionSlots( [ 'main' => $newSlot ] ); - $user = new UserIdentityValue( intval( $row['rev_user'] ), $row['rev_user_text'] ); - $rev = new RevisionStoreRecord( $title, $user, @@ -573,15 +594,17 @@ class RevisionStore if ( $current ) { $fields = [ - 'page' => $title->getArticleID(), - 'user_text' => $user->getName(), - 'user' => $user->getId(), - 'comment' => $comment, - 'minor_edit' => $minor, - 'text_id' => $current->rev_text_id, - 'parent_id' => $current->page_latest, - 'len' => $current->rev_len, - 'sha1' => $current->rev_sha1 + 'page' => $title->getArticleID(), + 'user_text' => $user->getName(), + 'user' => $user->getId(), + 'actor' => $user->getActorId(), + 'comment' => $comment, + 'minor_edit' => $minor, + 'text_id' => $current->rev_text_id, + 'parent_id' => $current->page_latest, + 'slot_origin' => $current->page_latest, + 'len' => $current->rev_len, + 'sha1' => $current->rev_sha1 ]; if ( $this->contentHandlerUseDB ) { @@ -591,7 +614,7 @@ class RevisionStore $fields['title'] = Title::makeTitle( $current->page_namespace, $current->page_title ); - $mainSlot = $this->emulateMainSlot_1_29( $fields, 0, $title ); + $mainSlot = $this->emulateMainSlot_1_29( $fields, self::READ_LATEST, $title ); $revision = new MutableRevisionRecord( $title, $this->wikiId ); $this->initializeMutableRevisionFromArray( $revision, $fields ); $revision->setSlot( $mainSlot ); @@ -647,9 +670,10 @@ class RevisionStore } // TODO: Select by rc_this_oldid alone - but as of Nov 2017, there is no index on that! + $actorWhere = $this->actorMigration->getWhere( $dbr, 'rc_user', $rev->getUser(), false ); $rc = RecentChange::newFromConds( [ - 'rc_user_text' => $userIdentity->getName(), + $actorWhere['conds'], 'rc_timestamp' => $dbr->timestamp( $rev->getTimestamp() ), 'rc_this_oldid' => $rev->getId() ], @@ -684,6 +708,7 @@ class RevisionStore 'ar_timestamp' => 'rev_timestamp', 'ar_user_text' => 'rev_user_text', 'ar_user' => 'rev_user', + 'ar_actor' => 'rev_actor', 'ar_minor_edit' => 'rev_minor_edit', 'ar_deleted' => 'rev_deleted', 'ar_len' => 'rev_len', @@ -727,6 +752,10 @@ class RevisionStore private function emulateMainSlot_1_29( $row, $queryFlags, Title $title ) { $mainSlotRow = new stdClass(); $mainSlotRow->role_name = 'main'; + $mainSlotRow->model_name = null; + $mainSlotRow->slot_revision_id = null; + $mainSlotRow->content_address = null; + $mainSlotRow->slot_content_id = null; $content = null; $blobData = null; @@ -734,16 +763,20 @@ class RevisionStore if ( is_object( $row ) ) { // archive row - if ( !isset( $row->rev_id ) && isset( $row->ar_user ) ) { + if ( !isset( $row->rev_id ) && ( isset( $row->ar_user ) || isset( $row->ar_actor ) ) ) { $row = $this->mapArchiveFields( $row ); } if ( isset( $row->rev_text_id ) && $row->rev_text_id > 0 ) { - $mainSlotRow->cont_address = 'tt:' . $row->rev_text_id; - } elseif ( isset( $row->ar_id ) ) { - $mainSlotRow->cont_address = 'ar:' . $row->ar_id; + $mainSlotRow->slot_content_id = $row->rev_text_id; + $mainSlotRow->content_address = 'tt:' . $row->rev_text_id; } + // This is used by null-revisions + $mainSlotRow->slot_origin = isset( $row->slot_origin ) + ? intval( $row->slot_origin ) + : null; + if ( isset( $row->old_text ) ) { // this happens when the text-table gets joined directly, in the pre-1.30 schema $blobData = isset( $row->old_text ) ? strval( $row->old_text ) : null; @@ -754,10 +787,10 @@ class RevisionStore $blobFlags = ( $row->old_flags === null ) ? '' : $row->old_flags; } - $mainSlotRow->slot_revision = intval( $row->rev_id ); + $mainSlotRow->slot_revision_id = intval( $row->rev_id ); - $mainSlotRow->cont_size = isset( $row->rev_len ) ? intval( $row->rev_len ) : null; - $mainSlotRow->cont_sha1 = isset( $row->rev_sha1 ) ? strval( $row->rev_sha1 ) : null; + $mainSlotRow->content_size = isset( $row->rev_len ) ? intval( $row->rev_len ) : null; + $mainSlotRow->content_sha1 = isset( $row->rev_sha1 ) ? strval( $row->rev_sha1 ) : null; $mainSlotRow->model_name = isset( $row->rev_content_model ) ? strval( $row->rev_content_model ) : null; @@ -766,13 +799,19 @@ class RevisionStore ? strval( $row->rev_content_format ) : null; } elseif ( is_array( $row ) ) { - $mainSlotRow->slot_revision = isset( $row['id'] ) ? intval( $row['id'] ) : null; + $mainSlotRow->slot_revision_id = isset( $row['id'] ) ? intval( $row['id'] ) : null; - $mainSlotRow->cont_address = isset( $row['text_id'] ) + $mainSlotRow->slot_content_id = isset( $row['text_id'] ) + ? intval( $row['text_id'] ) + : null; + $mainSlotRow->slot_origin = isset( $row['slot_origin'] ) + ? intval( $row['slot_origin'] ) + : null; + $mainSlotRow->content_address = isset( $row['text_id'] ) ? 'tt:' . intval( $row['text_id'] ) : null; - $mainSlotRow->cont_size = isset( $row['len'] ) ? intval( $row['len'] ) : null; - $mainSlotRow->cont_sha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null; + $mainSlotRow->content_size = isset( $row['len'] ) ? intval( $row['len'] ) : null; + $mainSlotRow->content_sha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null; $mainSlotRow->model_name = isset( $row['content_model'] ) ? strval( $row['content_model'] ) : null; // XXX: must be a string! @@ -805,9 +844,11 @@ class RevisionStore throw new MWException( 'Revision constructor passed invalid row format.' ); } - // With the old schema, the content changes with every revision. - // ...except for null-revisions. Would be nice if we could detect them. - $mainSlotRow->slot_inherited = 0; + // With the old schema, the content changes with every revision, + // except for null-revisions. + if ( !isset( $mainSlotRow->slot_origin ) ) { + $mainSlotRow->slot_origin = $mainSlotRow->slot_revision_id; + } if ( $mainSlotRow->model_name === null ) { $mainSlotRow->model_name = function ( SlotRecord $slot ) use ( $title ) { @@ -831,6 +872,7 @@ class RevisionStore }; } + $mainSlotRow->slot_id = $mainSlotRow->slot_revision_id; return new SlotRecord( $mainSlotRow, $content ); } @@ -1013,9 +1055,10 @@ class RevisionStore * @return RevisionRecord|null */ public function getRevisionByTimestamp( $title, $timestamp ) { + $db = $this->getDBConnection( DB_REPLICA ); return $this->newRevisionFromConds( [ - 'rev_timestamp' => $timestamp, + 'rev_timestamp' => $db->timestamp( $timestamp ), 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey() ], @@ -1075,7 +1118,16 @@ class RevisionStore $row->$field = $value; } - $user = $this->getUserIdentityFromRowObject( $row, 'ar_' ); + try { + $user = User::newFromAnyId( + isset( $row->ar_user ) ? $row->ar_user : null, + isset( $row->ar_user_text ) ? $row->ar_user_text : null, + isset( $row->ar_actor ) ? $row->ar_actor : null + ); + } catch ( InvalidArgumentException $ex ) { + wfWarn( __METHOD__ . ': ' . $ex->getMessage() ); + $user = new UserIdentityValue( 0, '', 0 ); + } $comment = $this->commentStore // Legacy because $row may have come from self::selectFields() @@ -1087,34 +1139,6 @@ class RevisionStore return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId ); } - /** - * @param object $row - * @param string $prefix Field prefix, such as 'rev_' or 'ar_'. - * - * @return UserIdentityValue - */ - private function getUserIdentityFromRowObject( $row, $prefix = 'rev_' ) { - $idField = "{$prefix}user"; - $nameField = "{$prefix}user_text"; - - $userId = intval( $row->$idField ); - - if ( isset( $row->user_name ) ) { - $userName = $row->user_name; - } elseif ( isset( $row->$nameField ) ) { - $userName = $row->$nameField; - } else { - $userName = User::whoIs( $userId ); - } - - if ( $userName === false ) { - wfWarn( __METHOD__ . ': Cannot determine user name for user ID ' . $userId ); - $userName = ''; - } - - return new UserIdentityValue( $userId, $userName ); - } - /** * @see RevisionFactory::newRevisionFromRow_1_29 * @@ -1145,7 +1169,16 @@ class RevisionStore } } - $user = $this->getUserIdentityFromRowObject( $row ); + try { + $user = User::newFromAnyId( + isset( $row->rev_user ) ? $row->rev_user : null, + isset( $row->rev_user_text ) ? $row->rev_user_text : null, + isset( $row->rev_actor ) ? $row->rev_actor : null + ); + } catch ( InvalidArgumentException $ex ) { + wfWarn( __METHOD__ . ': ' . $ex->getMessage() ); + $user = new UserIdentityValue( 0, '', 0 ); + } $comment = $this->commentStore // Legacy because $row may have come from self::selectFields() @@ -1224,27 +1257,6 @@ class RevisionStore } } - // Replaces old lazy loading logic in Revision::getUserText. - if ( !isset( $fields['user_text'] ) && isset( $fields['user'] ) ) { - if ( $fields['user'] instanceof UserIdentity ) { - /** @var User $user */ - $user = $fields['user']; - $fields['user_text'] = $user->getName(); - $fields['user'] = $user->getId(); - } else { - // TODO: wrap this in a callback to make it lazy again. - $name = $fields['user'] === 0 ? false : User::whoIs( $fields['user'] ); - - if ( $name === false ) { - throw new MWException( - 'user_text not given, and unknown user ID ' . $fields['user'] - ); - } - - $fields['user_text'] = $name; - } - } - if ( isset( $fields['comment'] ) && !( $fields['comment'] instanceof CommentStoreComment ) @@ -1287,16 +1299,15 @@ class RevisionStore if ( isset( $fields['user'] ) && ( $fields['user'] instanceof UserIdentity ) ) { $user = $fields['user']; - } elseif ( isset( $fields['user'] ) && isset( $fields['user_text'] ) ) { - $user = new UserIdentityValue( intval( $fields['user'] ), $fields['user_text'] ); - } elseif ( isset( $fields['user'] ) ) { - $user = User::newFromId( intval( $fields['user'] ) ); - } elseif ( isset( $fields['user_text'] ) ) { - $user = User::newFromName( $fields['user_text'] ); - - // User::newFromName will return false for IP addresses (and invalid names) - if ( $user == false ) { - $user = new UserIdentityValue( 0, $fields['user_text'] ); + } else { + try { + $user = User::newFromAnyId( + isset( $fields['user'] ) ? $fields['user'] : null, + isset( $fields['user_text'] ) ? $fields['user_text'] : null, + isset( $fields['actor'] ) ? $fields['actor'] : null + ); + } catch ( InvalidArgumentException $ex ) { + $user = null; } } @@ -1613,8 +1624,6 @@ class RevisionStore 'rev_page', 'rev_text_id', 'rev_timestamp', - 'rev_user_text', - 'rev_user', 'rev_minor_edit', 'rev_deleted', 'rev_len', @@ -1627,6 +1636,11 @@ class RevisionStore $ret['fields'] = array_merge( $ret['fields'], $commentQuery['fields'] ); $ret['joins'] = array_merge( $ret['joins'], $commentQuery['joins'] ); + $actorQuery = $this->actorMigration->getJoin( 'rev_user' ); + $ret['tables'] = array_merge( $ret['tables'], $actorQuery['tables'] ); + $ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] ); + $ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] ); + if ( $this->contentHandlerUseDB ) { $ret['fields'][] = 'rev_content_format'; $ret['fields'][] = 'rev_content_model'; @@ -1650,7 +1664,8 @@ class RevisionStore $ret['fields'] = array_merge( $ret['fields'], [ 'user_name', ] ); - $ret['joins']['user'] = [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ]; + $u = $actorQuery['fields']['rev_user']; + $ret['joins']['user'] = [ 'LEFT JOIN', [ "$u != 0", "user_id = $u" ] ]; } if ( in_array( 'text', $options, true ) ) { @@ -1680,8 +1695,9 @@ class RevisionStore */ public function getArchiveQueryInfo() { $commentQuery = $this->commentStore->getJoin( 'ar_comment' ); + $actorQuery = $this->actorMigration->getJoin( 'ar_user' ); $ret = [ - 'tables' => [ 'archive' ] + $commentQuery['tables'], + 'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'], 'fields' => [ 'ar_id', 'ar_page_id', @@ -1691,15 +1707,13 @@ class RevisionStore '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'], + ] + $commentQuery['fields'] + $actorQuery['fields'], + 'joins' => $commentQuery['joins'] + $actorQuery['joins'], ]; if ( $this->contentHandlerUseDB ) { @@ -1922,15 +1936,19 @@ class RevisionStore return false; } + $revQuery = self::getQueryInfo(); $res = $db->select( - 'revision', - 'rev_user', + $revQuery['tables'], + [ + 'rev_user' => $revQuery['fields']['rev_user'], + ], [ 'rev_page' => $pageId, 'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) ) ], __METHOD__, - [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] + [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ], + $revQuery['joins'] ); foreach ( $res as $row ) { if ( $row->rev_user != $userId ) {