jenkins-bot has submitted this change and it was merged. Change subject: Introduce EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK ......................................................................
Introduce EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK For getting data from slaves but explicitly fallback to master in case we didn't find data about a given entity. The new EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK has almost the same semantics EntityRevisionLookup::LATEST_FROM_SLAVE had. Please note that this doesn't not change the behavior for any callers, yet. Bug: T108929 Change-Id: Ic9ef30431f8ad23f70d84df8fe005d05e4fec9ab --- M lib/includes/Store/CachingEntityRevisionLookup.php M lib/includes/Store/EntityRevisionLookup.php M lib/includes/Store/Sql/PrefetchingWikiPageEntityMetaDataAccessor.php M lib/includes/Store/Sql/WikiPageEntityMetaDataAccessor.php M lib/includes/Store/Sql/WikiPageEntityMetaDataLookup.php M lib/includes/Store/Sql/WikiPageEntityRevisionLookup.php M lib/tests/phpunit/EntityRevisionLookupTest.php M lib/tests/phpunit/MockRepository.php M lib/tests/phpunit/Store/CachingEntityRevisionLookupTest.php M lib/tests/phpunit/Store/PrefetchingWikiPageEntityMetaDataAccessorTest.php M repo/includes/Api/EntityLoadingHelper.php M repo/includes/Api/EntitySavingHelper.php M repo/includes/Api/LinkTitles.php M repo/includes/EditEntity.php M repo/includes/Hooks/OutputPageBeforeHTMLHookHandler.php M repo/includes/Interactors/ItemMergeInteractor.php M repo/includes/Interactors/RedirectCreationInteractor.php M repo/includes/LinkedData/EntityDataRequestHandler.php M repo/includes/Specials/SpecialModifyEntity.php M repo/includes/UpdateRepo/UpdateRepoJob.php M repo/tests/phpunit/includes/EditEntityTest.php M repo/tests/phpunit/includes/Store/Sql/WikiPageEntityMetaDataLookupTest.php M repo/tests/phpunit/includes/Store/WikiPageEntityRevisionLookupTest.php 23 files changed, 382 insertions(+), 121 deletions(-) Approvals: Daniel Kinzler: Looks good to me, approved jenkins-bot: Verified diff --git a/lib/includes/Store/CachingEntityRevisionLookup.php b/lib/includes/Store/CachingEntityRevisionLookup.php index 5926e1d..26acf4e 100644 --- a/lib/includes/Store/CachingEntityRevisionLookup.php +++ b/lib/includes/Store/CachingEntityRevisionLookup.php @@ -6,6 +6,7 @@ use Wikibase\DataModel\Entity\EntityId; use Wikibase\DataModel\Entity\EntityRedirect; use Wikibase\EntityRevision; +use Wikimedia\Assert\Assert; /** * Implementation of EntityLookup that caches the obtained entities. @@ -96,24 +97,29 @@ * still the latest. Otherwise, any cached revision will be used if $revisionId=0. * * @param EntityId $entityId - * @param int|string $revisionId The desired revision id, or LATEST_FROM_SLAVE or LATEST_FROM_MASTER. + * @param int $revisionId The desired revision id, or 0 for the latest revision. + * @param string $mode LATEST_FROM_SLAVE, LATEST_FROM_SLAVE_WITH_FALLBACK or + * LATEST_FROM_MASTER. * * @throws StorageException * @return EntityRevision|null */ - public function getEntityRevision( EntityId $entityId, $revisionId = self::LATEST_FROM_SLAVE ) { - $key = $this->getCacheKey( $entityId ); + public function getEntityRevision( + EntityId $entityId, + $revisionId = 0, + $mode = self::LATEST_FROM_SLAVE_WITH_FALLBACK + ) { + Assert::parameterType( 'integer', $revisionId, '$revisionId' ); + Assert::parameterType( 'string', $mode, '$mode' ); - if ( $revisionId === 0 ) { - $revisionId = self::LATEST_FROM_SLAVE; - } + $key = $this->getCacheKey( $entityId ); /** @var EntityRevision $entityRevision */ $entityRevision = $this->cache->get( $key ); if ( $entityRevision !== false ) { - if ( !is_int( $revisionId ) && $this->shouldVerifyRevision ) { - $latestRevision = $this->lookup->getLatestRevisionId( $entityId, $revisionId ); + if ( $revisionId === 0 && $this->shouldVerifyRevision ) { + $latestRevision = $this->lookup->getLatestRevisionId( $entityId, $mode ); if ( $latestRevision === false ) { // entity no longer exists! @@ -123,13 +129,13 @@ } } - if ( is_int( $revisionId ) && $entityRevision && $entityRevision->getRevisionId() !== $revisionId ) { + if ( $revisionId > 0 && $entityRevision && $entityRevision->getRevisionId() !== $revisionId ) { $entityRevision = false; } } if ( $entityRevision === false ) { - $entityRevision = $this->fetchEntityRevision( $entityId, $revisionId ); + $entityRevision = $this->fetchEntityRevision( $entityId, $revisionId, $mode ); } return $entityRevision; @@ -139,16 +145,17 @@ * Fetches the EntityRevision and updates the cache accordingly. * * @param EntityId $entityId - * @param int|string $revisionId + * @param int $revisionId + * @param string $mode * * @throws StorageException * @return EntityRevision|null */ - private function fetchEntityRevision( EntityId $entityId, $revisionId ) { + private function fetchEntityRevision( EntityId $entityId, $revisionId, $mode ) { $key = $this->getCacheKey( $entityId ); - $entityRevision = $this->lookup->getEntityRevision( $entityId, $revisionId ); + $entityRevision = $this->lookup->getEntityRevision( $entityId, $revisionId, $mode ); - if ( !is_int( $revisionId ) ) { + if ( $revisionId === 0 ) { if ( $entityRevision === null ) { $this->cache->delete( $key ); } else { diff --git a/lib/includes/Store/EntityRevisionLookup.php b/lib/includes/Store/EntityRevisionLookup.php index c251e60..4f60668 100644 --- a/lib/includes/Store/EntityRevisionLookup.php +++ b/lib/includes/Store/EntityRevisionLookup.php @@ -23,6 +23,13 @@ const LATEST_FROM_SLAVE = 'slave'; /** + * Flag used to indicate that loading slightly lagged data is fine (like + * LATEST_FROM_SLAVE), but in case an entity or revision couldn't be found, + * we try loading it from master. + */ + const LATEST_FROM_SLAVE_WITH_FALLBACK = 'master_fallback'; + + /** * Flag to use instead of a revision ID to indicate that the latest revision is desired, * and it is essential to assert that there really is no newer version, to avoid data loss * or conflicts. This would generally be the case when loading an entity for @@ -41,15 +48,19 @@ * @since 0.4 * * @param EntityId $entityId - * @param int|string $revisionId The desired revision id, or LATEST_FROM_SLAVE or LATEST_FROM_MASTER - * to indicate that the latest revision is required. LATEST_FROM_MASTER would force the - * revision to be determined from the canonical master database. + * @param int $revisionId The desired revision id, or 0 for the latest revision. + * @param string $mode LATEST_FROM_SLAVE, LATEST_FROM_SLAVE_WITH_FALLBACK or + * LATEST_FROM_MASTER. * * @throws RevisionedUnresolvedRedirectException * @throws StorageException * @return EntityRevision|null */ - public function getEntityRevision( EntityId $entityId, $revisionId = self::LATEST_FROM_SLAVE ); + public function getEntityRevision( + EntityId $entityId, + $revisionId = 0, + $mode = self::LATEST_FROM_SLAVE_WITH_FALLBACK + ); /** * Returns the id of the latest revision of the given entity, or false if there is no such entity. diff --git a/lib/includes/Store/Sql/PrefetchingWikiPageEntityMetaDataAccessor.php b/lib/includes/Store/Sql/PrefetchingWikiPageEntityMetaDataAccessor.php index 2072c0d..77fd538 100644 --- a/lib/includes/Store/Sql/PrefetchingWikiPageEntityMetaDataAccessor.php +++ b/lib/includes/Store/Sql/PrefetchingWikiPageEntityMetaDataAccessor.php @@ -137,7 +137,9 @@ * @see WikiPageEntityMetaDataAccessor::loadRevisionInformation * * @param EntityId[] $entityIds - * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE or EntityRevisionLookup::LATEST_FROM_MASTER) + * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE, + * EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK or + * EntityRevisionLookup::LATEST_FROM_MASTER) * * @return stdClass[] Array of entity id serialization => object. */ @@ -159,7 +161,7 @@ } $this->prefetch( $entityIds ); - $this->doFetch(); + $this->doFetch( $mode ); $data = array(); foreach ( $entityIds as $entityId ) { @@ -174,23 +176,27 @@ * * @param EntityId $entityId * @param int $revisionId + * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE, + * EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK or + * EntityRevisionLookup::LATEST_FROM_MASTER) * * @return stdClass|bool false if no such entity exists */ - public function loadRevisionInformationByRevisionId( EntityId $entityId, $revisionId ) { + public function loadRevisionInformationByRevisionId( + EntityId $entityId, + $revisionId, + $mode = EntityRevisionLookup::LATEST_FROM_MASTER + ) { // Caching this would have little or no benefit, but would be rather complex. - return $this->lookup->loadRevisionInformationByRevisionId( $entityId, $revisionId ); + return $this->lookup->loadRevisionInformationByRevisionId( $entityId, $revisionId, $mode ); } - private function doFetch() { + private function doFetch( $mode ) { if ( empty( $this->toFetch ) ) { return; } - $data = $this->lookup->loadRevisionInformation( - $this->toFetch, - EntityRevisionLookup::LATEST_FROM_SLAVE - ); + $data = $this->lookup->loadRevisionInformation( $this->toFetch, $mode ); // Store the data, including cache misses $this->store( $data ); diff --git a/lib/includes/Store/Sql/WikiPageEntityMetaDataAccessor.php b/lib/includes/Store/Sql/WikiPageEntityMetaDataAccessor.php index 82a36a0..2801478 100644 --- a/lib/includes/Store/Sql/WikiPageEntityMetaDataAccessor.php +++ b/lib/includes/Store/Sql/WikiPageEntityMetaDataAccessor.php @@ -4,6 +4,7 @@ use stdClass; use Wikibase\DataModel\Entity\EntityId; +use Wikibase\Lib\Store\EntityRevisionLookup; /** * Interface for services giving access to meta data about one or more entities as needed for @@ -23,7 +24,9 @@ * 'page_latest', 'old_id', 'old_text' and 'old_flags'. * * @param EntityId[] $entityIds - * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE or EntityRevisionLookup::LATEST_FROM_MASTER) + * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE, + * EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK or + * EntityRevisionLookup::LATEST_FROM_MASTER) * * @return stdClass[] Array of entity id serialization => object. */ @@ -34,12 +37,22 @@ * revision of the entity or to load entity content from a MediaWiki revision. Included fields are * 'rev_id', 'rev_content_format', 'rev_timestamp', 'page_latest', 'old_id', 'old_text' * and 'old_flags'. + * Given that revision are immutable, this function will always try to load a revision from + * slave first and only use the master (with EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK + * or EntityRevisionLookup::LATEST_FROM_MASTER) in case the revision couldn't be found. * * @param EntityId $entityId * @param int $revisionId Revision id to fetch data about, must be an integer greater than 0. + * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE, + * EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK or + * EntityRevisionLookup::LATEST_FROM_MASTER). * * @return stdClass|bool false if no such entity exists */ - public function loadRevisionInformationByRevisionId( EntityId $entityId, $revisionId ); + public function loadRevisionInformationByRevisionId( + EntityId $entityId, + $revisionId, + $mode = EntityRevisionLookup::LATEST_FROM_MASTER + ); } diff --git a/lib/includes/Store/Sql/WikiPageEntityMetaDataLookup.php b/lib/includes/Store/Sql/WikiPageEntityMetaDataLookup.php index 65ec67d..01669eb 100644 --- a/lib/includes/Store/Sql/WikiPageEntityMetaDataLookup.php +++ b/lib/includes/Store/Sql/WikiPageEntityMetaDataLookup.php @@ -8,8 +8,8 @@ use ResultWrapper; use stdClass; use Wikibase\DataModel\Entity\EntityId; -use Wikibase\Lib\Store\EntityRevisionLookup; use Wikibase\Lib\Store\EntityNamespaceLookup; +use Wikibase\Lib\Store\EntityRevisionLookup; /** * Service for looking up meta data about one or more entities as needed for @@ -43,30 +43,38 @@ /** * @param EntityId[] $entityIds - * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE or EntityRevisionLookup::LATEST_FROM_MASTER) + * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE, + * EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK or + * EntityRevisionLookup::LATEST_FROM_MASTER) * * @throws DBQueryError * @return stdClass[] Array of entity id serialization => object. */ - public function loadRevisionInformation( array $entityIds, $mode ) { + public function loadRevisionInformation( + array $entityIds, + $mode + ) { $rows = array(); if ( $mode !== EntityRevisionLookup::LATEST_FROM_MASTER ) { $rows = $this->selectRevisionInformationMultiple( $entityIds, DB_SLAVE ); } - $loadFromMaster = array(); - foreach ( $entityIds as $entityId ) { - if ( !isset( $rows[$entityId->getSerialization()] ) || !$rows[$entityId->getSerialization()] ) { - $loadFromMaster[] = $entityId; + if ( $mode !== EntityRevisionLookup::LATEST_FROM_SLAVE ) { + // Attempt to load (missing) rows from master if the caller asked for that. + $loadFromMaster = array(); + foreach ( $entityIds as $entityId ) { + if ( !isset( $rows[$entityId->getSerialization()] ) || !$rows[$entityId->getSerialization()] ) { + $loadFromMaster[] = $entityId; + } } - } - if ( $loadFromMaster ) { - $rows = array_merge( - $rows, - $this->selectRevisionInformationMultiple( $loadFromMaster, DB_MASTER ) - ); + if ( $loadFromMaster ) { + $rows = array_merge( + $rows, + $this->selectRevisionInformationMultiple( $loadFromMaster, DB_MASTER ) + ); + } } return $rows; @@ -75,15 +83,22 @@ /** * @param EntityId $entityId * @param int $revisionId + * @param string $mode (WikiPageEntityMetaDataAccessor::LATEST_FROM_SLAVE, + * WikiPageEntityMetaDataAccessor::LATEST_FROM_SLAVE_WITH_FALLBACK or + * WikiPageEntityMetaDataAccessor::LATEST_FROM_MASTER) * * @throws DBQueryError * @return stdClass|bool */ - public function loadRevisionInformationByRevisionId( EntityId $entityId, $revisionId ) { + public function loadRevisionInformationByRevisionId( + EntityId $entityId, + $revisionId, + $mode = EntityRevisionLookup::LATEST_FROM_MASTER + ) { $row = $this->selectRevisionInformationById( $entityId, $revisionId, DB_SLAVE ); - if ( !$row ) { - // Try loading from master + if ( !$row && $mode !== EntityRevisionLookup::LATEST_FROM_SLAVE ) { + // Try loading from master, unless the caller only wants slave data. wfDebugLog( __CLASS__, __FUNCTION__ . ': try to load ' . $entityId . " with $revisionId from DB_MASTER." ); diff --git a/lib/includes/Store/Sql/WikiPageEntityRevisionLookup.php b/lib/includes/Store/Sql/WikiPageEntityRevisionLookup.php index 7f529bd..8c19757 100644 --- a/lib/includes/Store/Sql/WikiPageEntityRevisionLookup.php +++ b/lib/includes/Store/Sql/WikiPageEntityRevisionLookup.php @@ -10,6 +10,7 @@ use Wikibase\DataModel\Entity\EntityRedirect; use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataAccessor; use Wikibase\EntityRevision; +use Wikimedia\Assert\Assert; /** * Implements an entity repo based on blobs stored in wiki pages on a locally reachable @@ -55,30 +56,32 @@ * @see EntityRevisionLookup::getEntityRevision * * @param EntityId $entityId - * @param int|string $revisionId The desired revision id, or LATEST_FROM_SLAVE or LATEST_FROM_MASTER. + * @param int $revisionId The desired revision id, or 0 for the latest revision. + * @param string $mode LATEST_FROM_SLAVE, LATEST_FROM_SLAVE_WITH_FALLBACK or + * LATEST_FROM_MASTER. * * @throws RevisionedUnresolvedRedirectException * @throws StorageException * @return EntityRevision|null */ - public function getEntityRevision( EntityId $entityId, $revisionId = self::LATEST_FROM_SLAVE ) { + public function getEntityRevision( + EntityId $entityId, + $revisionId = 0, + $mode = self::LATEST_FROM_SLAVE_WITH_FALLBACK + ) { + Assert::parameterType( 'integer', $revisionId, '$revisionId' ); + Assert::parameterType( 'string', $mode, '$mode' ); + wfDebugLog( __CLASS__, __FUNCTION__ . ': Looking up entity ' . $entityId . " (revision $revisionId)." ); - - // default changed from false to 0 and then to LATEST_FROM_SLAVE - if ( $revisionId === false || $revisionId === 0 ) { - wfWarn( 'getEntityRevision() called with $revisionId = false or 0, ' . - 'use EntityRevisionLookup::LATEST_FROM_SLAVE or EntityRevisionLookup::LATEST_FROM_MASTER instead.' ); - $revisionId = self::LATEST_FROM_SLAVE; - } /** @var EntityRevision $entityRevision */ $entityRevision = null; - if ( is_int( $revisionId ) ) { - $row = $this->entityMetaDataAccessor->loadRevisionInformationByRevisionId( $entityId, $revisionId ); + if ( $revisionId > 0 ) { + $row = $this->entityMetaDataAccessor->loadRevisionInformationByRevisionId( $entityId, $revisionId, $mode ); } else { - $rows = $this->entityMetaDataAccessor->loadRevisionInformation( array( $entityId ), $revisionId ); + $rows = $this->entityMetaDataAccessor->loadRevisionInformation( array( $entityId ), $mode ); $row = $rows[$entityId->getSerialization()]; } @@ -111,11 +114,11 @@ $actualEntityId = $entityRevision->getEntity()->getId()->getSerialization(); // Get the revision id we actually loaded, if none was passed explicitly - $revisionId = is_int( $revisionId ) ? $revisionId : $entityRevision->getRevisionId(); + $revisionId = $revisionId ?: $entityRevision->getRevisionId(); throw new BadRevisionException( "Revision $revisionId belongs to $actualEntityId instead of expected $entityId" ); } - if ( is_int( $revisionId ) && $entityRevision === null ) { + if ( $revisionId > 0 && $entityRevision === null ) { // If a revision ID was specified, but that revision doesn't exist: throw new BadRevisionException( "No such revision found for $entityId: $revisionId" ); } diff --git a/lib/tests/phpunit/EntityRevisionLookupTest.php b/lib/tests/phpunit/EntityRevisionLookupTest.php index 40e987e..1599b5a 100644 --- a/lib/tests/phpunit/EntityRevisionLookupTest.php +++ b/lib/tests/phpunit/EntityRevisionLookupTest.php @@ -84,7 +84,7 @@ public function provideGetEntityRevision() { $cases = array( array( // #0: any revision - new ItemId( 'q42' ), EntityRevisionLookup::LATEST_FROM_SLAVE, true, + new ItemId( 'q42' ), 0, true, ), array( // #1: first revision new ItemId( 'q42' ), 11, true, @@ -96,13 +96,13 @@ new ItemId( 'q42' ), 600000, false, StorageException::class, ), array( // #4: wrong type - new ItemId( 'q753' ), EntityRevisionLookup::LATEST_FROM_SLAVE, false, + new ItemId( 'q753' ), 0, false, ), array( // #5: mismatching revision new PropertyId( 'p753' ), 11, false, StorageException::class, ), array( // #6: some revision - new PropertyId( 'p753' ), EntityRevisionLookup::LATEST_FROM_SLAVE, true, + new PropertyId( 'p753' ), 0, true, ), ); @@ -113,7 +113,7 @@ * @dataProvider provideGetEntityRevision * * @param EntityId $id The entity to get - * @param int $revision The revision to get (or LATEST_FROM_SLAVE or LATEST_FROM_MASTER) + * @param int $revision The revision to get (or 0 for latest) * @param bool $shouldExist * @param string|null $expectException */ diff --git a/lib/tests/phpunit/MockRepository.php b/lib/tests/phpunit/MockRepository.php index 102f59a..9f6acee 100644 --- a/lib/tests/phpunit/MockRepository.php +++ b/lib/tests/phpunit/MockRepository.php @@ -121,13 +121,19 @@ * @see EntityRevisionLookup::getEntityRevision * * @param EntityId $entityId - * @param int|string $revisionId The desired revision id, or LATEST_FROM_SLAVE or LATEST_FROM_MASTER. + * @param int $revisionId The desired revision id, or 0 for the latest revision. + * @param string $mode LATEST_FROM_SLAVE, LATEST_FROM_SLAVE_WITH_FALLBACK or + * LATEST_FROM_MASTER. * * @throws RevisionedUnresolvedRedirectException * @throws StorageException * @return EntityRevision|null */ - public function getEntityRevision( EntityId $entityId, $revisionId = self::LATEST_FROM_SLAVE ) { + public function getEntityRevision( + EntityId $entityId, + $revisionId = 0, + $mode = self::LATEST_FROM_SLAVE_WITH_FALLBACK + ) { $key = $entityId->getSerialization(); if ( isset( $this->redirects[$key] ) ) { @@ -144,17 +150,15 @@ return null; } - // default changed from false to 0 and then to LATEST_FROM_SLAVE - if ( $revisionId === false || $revisionId === 0 ) { - wfWarn( 'getEntityRevision() called with $revisionId = false or 0, ' . - 'use EntityRevisionLookup::LATEST_FROM_SLAVE or EntityRevisionLookup::LATEST_FROM_MASTER instead.' ); - $revisionId = self::LATEST_FROM_SLAVE; + if ( !is_int( $revisionId ) ) { + wfWarn( 'getEntityRevision() called with $revisionId = false or a string, use 0 instead.' ); + $revisionId = 0; } /** @var EntityRevision[] $revisions */ $revisions = $this->entities[$key]; - if ( !is_int( $revisionId ) ) { + if ( $revisionId === 0 ) { $revisionIds = array_keys( $revisions ); $revisionId = end( $revisionIds ); } elseif ( !isset( $revisions[$revisionId] ) ) { @@ -467,7 +471,7 @@ */ public function getLatestRevisionId( EntityId $entityId, $mode = self::LATEST_FROM_SLAVE ) { try { - $revision = $this->getEntityRevision( $entityId, $mode ); + $revision = $this->getEntityRevision( $entityId, 0, $mode ); } catch ( RevisionedUnresolvedRedirectException $e ) { return false; } diff --git a/lib/tests/phpunit/Store/CachingEntityRevisionLookupTest.php b/lib/tests/phpunit/Store/CachingEntityRevisionLookupTest.php index de1aa08..dd91cb2 100644 --- a/lib/tests/phpunit/Store/CachingEntityRevisionLookupTest.php +++ b/lib/tests/phpunit/Store/CachingEntityRevisionLookupTest.php @@ -9,6 +9,7 @@ use Wikibase\DataModel\Services\Lookup\EntityLookup; use Wikibase\EntityRevision; use Wikibase\Lib\Store\CachingEntityRevisionLookup; +use Wikibase\Lib\Store\EntityRevisionLookup; use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException; use Wikibase\Lib\Tests\EntityRevisionLookupTest; use Wikibase\Lib\Tests\MockRepository; @@ -47,6 +48,25 @@ return new CachingEntityRevisionLookup( $mock, new HashBagOStuff() ); } + public function testGetEntityRevision_byRevisionIdWithMode() { + $id = new ItemId( 'Q123' ); + $item = new Item( $id ); + + $mock = $this->getMock( EntityRevisionLookup::class ); + $mock->expects( $this->once() ) + ->method( 'getEntityRevision' ) + ->with( $id, 1234, 'load-mode' ) + ->will( $this->returnValue( $item ) ); + + $lookup = new CachingEntityRevisionLookup( $mock, new HashBagOStuff() ); + $lookup->setVerifyRevision( false ); + + $this->assertSame( + $item, + $lookup->getEntityRevision( $id, 1234, 'load-mode' ) + ); + } + public function testWithRevisionVerification() { $mock = new MockRepository(); diff --git a/lib/tests/phpunit/Store/PrefetchingWikiPageEntityMetaDataAccessorTest.php b/lib/tests/phpunit/Store/PrefetchingWikiPageEntityMetaDataAccessorTest.php index c2585d6..5f26412 100644 --- a/lib/tests/phpunit/Store/PrefetchingWikiPageEntityMetaDataAccessorTest.php +++ b/lib/tests/phpunit/Store/PrefetchingWikiPageEntityMetaDataAccessorTest.php @@ -33,10 +33,14 @@ $lookup = $this->getMock( WikiPageEntityMetaDataAccessor::class ); $lookup->expects( $this->once() ) ->method( 'loadRevisionInformation' ) - ->with( array( - $q1->getSerialization() => $q1, - $q3->getSerialization() => $q3, - $q2->getSerialization() => $q2 ) ) + ->with( + array( + $q1->getSerialization() => $q1, + $q3->getSerialization() => $q3, + $q2->getSerialization() => $q2 + ), + $fromSlave + ) ->will( $this->returnValue( array( 'Q1' => 'Nyan', 'Q2' => 'cat', @@ -193,6 +197,29 @@ ); } + /** + * Make sure we do the actual fetch with the right $mode set. + */ + public function testLoadRevisionInformation_mode() { + $q1 = new ItemId( 'Q1' ); + + $lookup = $this->getMock( WikiPageEntityMetaDataAccessor::class ); + $lookup->expects( $this->once() ) + ->method( 'loadRevisionInformation' ) + ->with( + array( $q1->getSerialization() => $q1 ), + 'load-mode' + ) + ->will( $this->returnValue( array( 'Q1' => 'data' ) ) ); + + $accessor = new PrefetchingWikiPageEntityMetaDataAccessor( $lookup ); + + // This loads Q1 with $mode = 'load-mode' + $result = $accessor->loadRevisionInformation( array( $q1 ), 'load-mode' ); + + $this->assertSame( array( 'Q1' => 'data' ), $result ); + } + public function testLoadRevisionInformationByRevisionId() { // This function is a very simple, it's just a wrapper around the // lookup function. @@ -201,7 +228,7 @@ $lookup = $this->getMock( WikiPageEntityMetaDataAccessor::class ); $lookup->expects( $this->once() ) ->method( 'loadRevisionInformationByRevisionId' ) - ->with( $q1, 123 ) + ->with( $q1, 123, EntityRevisionLookup::LATEST_FROM_MASTER ) ->will( $this->returnValue( 'passthrough' ) ); $accessor = new PrefetchingWikiPageEntityMetaDataAccessor( $lookup ); diff --git a/repo/includes/Api/EntityLoadingHelper.php b/repo/includes/Api/EntityLoadingHelper.php index d2e6f3b..082b915 100644 --- a/repo/includes/Api/EntityLoadingHelper.php +++ b/repo/includes/Api/EntityLoadingHelper.php @@ -130,9 +130,9 @@ * cannot be found or cannot be loaded. * * @param EntityId $entityId EntityId of the page to load the revision for - * @param int|string|null $revId revision to load, or the retrieval mode, - * see the LATEST_XXX constants defined in EntityRevisionLookup. - * If not given, the current revision will be loaded, using the default retrieval mode. + * @param int $revId The desired revision id, or 0 for the latest revision. + * @param string|null $mode LATEST_FROM_SLAVE, LATEST_FROM_SLAVE_WITH_FALLBACK or + * LATEST_FROM_MASTER (from EntityRevisionLookup). Null for the default. * * @throws UsageException * @throws LogicException @@ -140,14 +140,18 @@ */ protected function loadEntityRevision( EntityId $entityId, - $revId = null + $revId = 0, + $mode = null ) { if ( $revId === null ) { - $revId = $this->defaultRetrievalMode; + $revId = 0; + } + if ( $mode === null ) { + $mode = $this->defaultRetrievalMode; } try { - $revision = $this->entityRevisionLookup->getEntityRevision( $entityId, $revId ); + $revision = $this->entityRevisionLookup->getEntityRevision( $entityId, $revId, $mode ); return $revision; } catch ( RevisionedUnresolvedRedirectException $ex ) { $this->errorReporter->dieException( $ex, 'unresolved-redirect' ); diff --git a/repo/includes/Api/EntitySavingHelper.php b/repo/includes/Api/EntitySavingHelper.php index d7ee757..9973005 100644 --- a/repo/includes/Api/EntitySavingHelper.php +++ b/repo/includes/Api/EntitySavingHelper.php @@ -153,14 +153,10 @@ ? (int)$params['baserevid'] : 0; - if ( $baseRev === 0 ) { - $baseRev = $this->defaultRetrievalMode; - } - if ( $entityId ) { $entityRevision = $this->loadEntityRevision( $entityId, $baseRev ); } else { - if ( is_int( $baseRev ) ) { + if ( $baseRev > 0 ) { $this->errorReporter->dieError( 'Cannot load specific revision ' . $baseRev . ' if no entity is defined.', 'param-illegal' @@ -172,7 +168,7 @@ $new = isset( $params['new'] ) ? $params['new'] : null; if ( is_null( $entityRevision ) ) { - if ( is_int( $baseRev ) ) { + if ( $baseRev > 0 ) { $this->errorReporter->dieError( 'Could not find revision ' . $baseRev, 'nosuchrevid' diff --git a/repo/includes/Api/LinkTitles.php b/repo/includes/Api/LinkTitles.php index ea7f272..97b01e7 100644 --- a/repo/includes/Api/LinkTitles.php +++ b/repo/includes/Api/LinkTitles.php @@ -141,7 +141,7 @@ } elseif ( $fromId === null && $toId !== null ) { // reuse to-site's item /** @var Item $item */ - $itemRev = $lookup->getEntityRevision( $toId, EntityRevisionLookup::LATEST_FROM_MASTER ); + $itemRev = $lookup->getEntityRevision( $toId, 0, EntityRevisionLookup::LATEST_FROM_MASTER ); $item = $itemRev->getEntity(); $fromLink = new SiteLink( $fromSite->getGlobalId(), $fromPage ); $item->addSiteLink( $fromLink ); @@ -150,7 +150,7 @@ } elseif ( $fromId !== null && $toId === null ) { // reuse from-site's item /** @var Item $item */ - $itemRev = $lookup->getEntityRevision( $fromId, EntityRevisionLookup::LATEST_FROM_MASTER ); + $itemRev = $lookup->getEntityRevision( $fromId, 0, EntityRevisionLookup::LATEST_FROM_MASTER ); $item = $itemRev->getEntity(); $toLink = new SiteLink( $toSite->getGlobalId(), $toPage ); $item->addSiteLink( $toLink ); diff --git a/repo/includes/EditEntity.php b/repo/includes/EditEntity.php index 321d749..6eb21a1 100644 --- a/repo/includes/EditEntity.php +++ b/repo/includes/EditEntity.php @@ -278,6 +278,7 @@ // $this->getPage(), this should NOT change! $this->latestRev = $this->entityRevisionLookup->getEntityRevision( $id, + 0, EntityRevisionLookup::LATEST_FROM_MASTER ); } @@ -353,10 +354,6 @@ } elseif ( $baseRevId === $this->getLatestRevisionId() ) { $this->baseRev = $this->getLatestRevision(); } else { - if ( $baseRevId === 0 ) { - $baseRevId = EntityRevisionLookup::LATEST_FROM_MASTER; - } - $id = $this->newEntity->getId(); $this->baseRev = $this->entityRevisionLookup->getEntityRevision( $id, $baseRevId ); diff --git a/repo/includes/Hooks/OutputPageBeforeHTMLHookHandler.php b/repo/includes/Hooks/OutputPageBeforeHTMLHookHandler.php index 2ca9873..7a6cde2 100644 --- a/repo/includes/Hooks/OutputPageBeforeHTMLHookHandler.php +++ b/repo/includes/Hooks/OutputPageBeforeHTMLHookHandler.php @@ -188,7 +188,7 @@ /** * @param EntityId $entityId - * @param string $revisionId + * @param int $revisionId * @param bool $termsListPrerendered * * @return EntityDocument|null diff --git a/repo/includes/Interactors/ItemMergeInteractor.php b/repo/includes/Interactors/ItemMergeInteractor.php index 11f898a..e1e7af5 100644 --- a/repo/includes/Interactors/ItemMergeInteractor.php +++ b/repo/includes/Interactors/ItemMergeInteractor.php @@ -225,6 +225,7 @@ try { $revision = $this->entityRevisionLookup->getEntityRevision( $itemId, + 0, EntityRevisionLookup::LATEST_FROM_MASTER ); diff --git a/repo/includes/Interactors/RedirectCreationInteractor.php b/repo/includes/Interactors/RedirectCreationInteractor.php index c5edb5e..15309e1 100644 --- a/repo/includes/Interactors/RedirectCreationInteractor.php +++ b/repo/includes/Interactors/RedirectCreationInteractor.php @@ -171,7 +171,11 @@ */ private function checkCanCreateRedirect( EntityId $entityId ) { try { - $revision = $this->entityRevisionLookup->getEntityRevision( $entityId, EntityRevisionLookup::LATEST_FROM_MASTER ); + $revision = $this->entityRevisionLookup->getEntityRevision( + $entityId, + 0, + EntityRevisionLookup::LATEST_FROM_MASTER + ); if ( !$revision ) { $title = $this->entityTitleLookup->getTitleForId( $entityId ); diff --git a/repo/includes/LinkedData/EntityDataRequestHandler.php b/repo/includes/LinkedData/EntityDataRequestHandler.php index 323803d..1aa588d 100644 --- a/repo/includes/LinkedData/EntityDataRequestHandler.php +++ b/repo/includes/LinkedData/EntityDataRequestHandler.php @@ -344,11 +344,6 @@ */ private function getEntityRevision( EntityId $id, $revision ) { $prefixedId = $id->getSerialization(); - - if ( $revision === 0 ) { - $revision = EntityRevisionLookup::LATEST_FROM_SLAVE; - } - $redirectRevision = null; try { @@ -365,7 +360,7 @@ $ex->getRevisionId(), $ex->getRevisionTimestamp() ); - if ( is_string( $revision ) ) { + if ( $revision === 0 ) { // If no specific revision is requested, resolve the redirect. list( $entityRevision, ) = $this->getEntityRevision( $ex->getRedirectTargetId(), $revision ); } else { diff --git a/repo/includes/Specials/SpecialModifyEntity.php b/repo/includes/Specials/SpecialModifyEntity.php index ee00149..dd5ae88 100644 --- a/repo/includes/Specials/SpecialModifyEntity.php +++ b/repo/includes/Specials/SpecialModifyEntity.php @@ -186,15 +186,15 @@ * @since 0.5 * * @param EntityId $id - * @param int|string $revisionId * * @throws MessageException * @throws UserInputException * @return EntityRevision */ - protected function loadEntity( EntityId $id, $revisionId = EntityRevisionLookup::LATEST_FROM_MASTER ) { + protected function loadEntity( EntityId $id ) { try { - $entity = $this->entityRevisionLookup->getEntityRevision( $id, $revisionId ); + $entity = $this->entityRevisionLookup + ->getEntityRevision( $id, 0, EntityRevisionLookup::LATEST_FROM_MASTER ); if ( $entity === null ) { throw new UserInputException( diff --git a/repo/includes/UpdateRepo/UpdateRepoJob.php b/repo/includes/UpdateRepo/UpdateRepoJob.php index 4b1255a..91db8e7 100644 --- a/repo/includes/UpdateRepo/UpdateRepoJob.php +++ b/repo/includes/UpdateRepo/UpdateRepoJob.php @@ -120,7 +120,7 @@ } try { - $entityRevision = $this->entityRevisionLookup->getEntityRevision( $itemId, EntityRevisionLookup::LATEST_FROM_MASTER ); + $entityRevision = $this->entityRevisionLookup->getEntityRevision( $itemId, 0, EntityRevisionLookup::LATEST_FROM_MASTER ); } catch ( StorageException $ex ) { wfDebugLog( 'UpdateRepo', diff --git a/repo/tests/phpunit/includes/EditEntityTest.php b/repo/tests/phpunit/includes/EditEntityTest.php index 9f3879b..1f3f1f9 100644 --- a/repo/tests/phpunit/includes/EditEntityTest.php +++ b/repo/tests/phpunit/includes/EditEntityTest.php @@ -216,7 +216,7 @@ return array( array( // #0: case I: no base rev given. null, // input data - EntityRevisionLookup::LATEST_FROM_MASTER, // base rev + 0, // base rev false, // expected conflict false, // expected fix ), diff --git a/repo/tests/phpunit/includes/Store/Sql/WikiPageEntityMetaDataLookupTest.php b/repo/tests/phpunit/includes/Store/Sql/WikiPageEntityMetaDataLookupTest.php index b237d53..e7d7fd0 100644 --- a/repo/tests/phpunit/includes/Store/Sql/WikiPageEntityMetaDataLookupTest.php +++ b/repo/tests/phpunit/includes/Store/Sql/WikiPageEntityMetaDataLookupTest.php @@ -2,11 +2,15 @@ namespace Wikibase\Repo\Tests\Store\Sql; +use DatabaseBase; +use FakeResultWrapper; use MediaWikiTestCase; use Wikibase\DataModel\Entity\Item; use Wikibase\DataModel\Entity\ItemId; use Wikibase\EntityRevision; use Wikibase\Lib\Store\EntityNamespaceLookup; +use Wikibase\Lib\Store\EntityRevisionLookup; +use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataAccessor; use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataLookup; use Wikibase\Repo\WikibaseRepo; @@ -64,6 +68,72 @@ return new WikiPageEntityMetaDataLookup( $this->getEntityNamespaceLookup() ); } + /** + * This mock uses the real code except for DBAccessBase::getConnection + * + * @param int $selectCount Number of mocked/lagged DBAccessBase::getConnection::select calls + * @param int $selectRowCount Number of mocked/lagged DBAccessBase::getConnection::selectRow calls + * @param int $getConnectionCount Number of WikiPageEntityMetaDataLookup::getConnection calls + * + * @return WikiPageEntityMetaDataLookup + */ + private function getLookupWithLaggedConnection( $selectCount, $selectRowCount, $getConnectionCount ) { + $lookup = $this->getMockBuilder( WikiPageEntityMetaDataLookup::class ) + ->setConstructorArgs( array( $this->getEntityNamespaceLookup() ) ) + ->setMethods( array( 'getConnection' ) ) + ->getMock(); + + $lookup->expects( $this->exactly( $getConnectionCount ) ) + ->method( 'getConnection' ) + ->will( $this->returnCallback( function( $id ) use ( $selectCount, $selectRowCount ) { + $db = $realDB = wfGetDB( DB_MASTER ); + + if ( $id === DB_SLAVE ) { + // This is a (fake) lagged database connection. + $db = $this->getLaggedDatabase( $realDB, $selectCount, $selectRowCount ); + } + + return $db; + } ) ); + + return $lookup; + } + + /** + * Gets a "lagged" database connection: We always leave out the first row on select. + */ + private function getLaggedDatabase( DatabaseBase $realDB, $selectCount, $selectRowCount ) { + $db = $this->getMockBuilder( DatabaseBase::class ) + ->disableOriginalConstructor() + ->setMethods( array( 'select', 'selectRow' ) ) + ->setProxyTarget( $realDB ) + ->getMockForAbstractClass(); + + $db->expects( $this->exactly( $selectCount ) ) + ->method( 'select' ) + ->will( $this->returnCallback( function() use ( $realDB ) { + // Get the actual result + $res = call_user_func_array( + array( $realDB, 'select' ), + func_get_args() + ); + + // Return the real result minus the first row + $data = array(); + foreach ( $res as $row ) { + $data[] = $row; + } + + return new FakeResultWrapper( array_slice( $data, 1 ) ); + } ) ); + + $db->expects( $this->exactly( $selectRowCount ) ) + ->method( 'selectRow' ) + ->will( $this->returnValue( false ) ); + + return $db; + } + public function testLoadRevisionInformationById_latest() { $entityRevision = $this->data[0]; @@ -72,6 +142,39 @@ $this->assertEquals( $entityRevision->getRevisionId(), $result->rev_id ); $this->assertEquals( $entityRevision->getRevisionId(), $result->page_latest ); + } + + public function testLoadRevisionInformationById_masterFallback() { + $entityRevision = $this->data[0]; + + // Make sure we have two calls to getConnection: One that asks for a + // slave and one that asks for the master. + $lookup = $this->getLookupWithLaggedConnection( 0, 1, 2 ); + + $result = $lookup->loadRevisionInformationByRevisionId( + $entityRevision->getEntity()->getId(), + $entityRevision ->getRevisionId(), + EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK + ); + + $this->assertEquals( $entityRevision->getRevisionId(), $result->rev_id ); + $this->assertEquals( $entityRevision->getRevisionId(), $result->page_latest ); + } + + public function testLoadRevisionInformationById_noFallback() { + $entityRevision = $this->data[0]; + + // Should do only one getConnection call. + $lookup = $this->getLookupWithLaggedConnection( 0, 1, 1 ); + + $result = $lookup->loadRevisionInformationByRevisionId( + $entityRevision->getEntity()->getId(), + $entityRevision ->getRevisionId(), + EntityRevisionLookup::LATEST_FROM_SLAVE + ); + + // No fallback: Lagged data is omitted. + $this->assertFalse( $result ); } public function testLoadRevisionInformationById_old() { @@ -110,17 +213,7 @@ $this->assertFalse( $result ); } - public function testLoadRevisionInformation() { - $entityIds = array( - $this->data[0]->getEntity()->getId(), - $this->data[1]->getEntity()->getId(), - new ItemId( 'Q823487354' ), // Doesn't exist - $this->data[2]->getEntity()->getId() - ); - - $result = $this->getWikiPageEntityMetaDataLookup() - ->loadRevisionInformation( $entityIds, DB_SLAVE ); - + private function assertRevisionInformation( $entityIds, $result ) { $serializedEntityIds = array(); foreach ( $entityIds as $entityId ) { $serializedEntityIds[] = $entityId->getSerialization(); @@ -140,8 +233,45 @@ $result[$serializedEntityIds[3]]->rev_id, $this->data[2]->getRevisionId() ); - // Verify that Q823487354 (doesn't exist) is not part of the result - $this->assertFalse( $result['Q823487354'] ); + // Verify that no further entities are part of the result + $this->assertCount( count( $entityIds ), $result ); + } + + public function testLoadRevisionInformation() { + $entityIds = array( + $this->data[0]->getEntity()->getId(), + $this->data[1]->getEntity()->getId(), + new ItemId( 'Q823487354' ), // Doesn't exist + $this->data[2]->getEntity()->getId() + ); + + $result = $this->getWikiPageEntityMetaDataLookup() + ->loadRevisionInformation( + $entityIds, + EntityRevisionLookup::LATEST_FROM_SLAVE + ); + + $this->assertRevisionInformation( $entityIds, $result ); + } + + public function testLoadRevisionInformation_masterFallback() { + $entityIds = array( + $this->data[0]->getEntity()->getId(), + $this->data[1]->getEntity()->getId(), + new ItemId( 'Q823487354' ), // Doesn't exist + $this->data[2]->getEntity()->getId() + ); + + // Make sure we have two calls to getConnection: One that asks for a + // slave and one that asks for the master. + $lookup = $this->getLookupWithLaggedConnection( 1, 0, 2 ); + + $result = $lookup->loadRevisionInformation( + $entityIds, + EntityRevisionLookup::LATEST_FROM_SLAVE_WITH_FALLBACK + ); + + $this->assertRevisionInformation( $entityIds, $result ); } } diff --git a/repo/tests/phpunit/includes/Store/WikiPageEntityRevisionLookupTest.php b/repo/tests/phpunit/includes/Store/WikiPageEntityRevisionLookupTest.php index 4ed2916..506c5fa 100644 --- a/repo/tests/phpunit/includes/Store/WikiPageEntityRevisionLookupTest.php +++ b/repo/tests/phpunit/includes/Store/WikiPageEntityRevisionLookupTest.php @@ -126,4 +126,32 @@ $lookup->getEntityRevision( new ItemId( 'Q42' ) ); } + public function testGetEntityRevision_byRevisionIdWithMode() { + $testEntityRevision = reset( self::$testEntities ); + $entityId = $testEntityRevision->getEntity()->getId(); + $revisionId = $testEntityRevision->getRevisionId(); + + $realMetaDataLookup = new WikiPageEntityMetaDataLookup( $this->getEntityNamespaceLookup() ); + $metaDataLookup = $this->getMockBuilder( WikiPageEntityMetaDataLookup::class ) + ->disableOriginalConstructor() + ->getMock(); + + $metaDataLookup->expects( $this->once() ) + ->method( 'loadRevisionInformationByRevisionId' ) + ->with( $entityId, $revisionId, 'load-mode' ) + ->will( $this->returnValue( + $realMetaDataLookup->loadRevisionInformationByRevisionId( $entityId, $revisionId ) + ) ); + + $lookup = new WikiPageEntityRevisionLookup( + WikibaseRepo::getDefaultInstance()->getEntityContentDataCodec(), + $metaDataLookup, + false + ); + + $entityRevision = $lookup->getEntityRevision( $entityId, $revisionId, 'load-mode' ); + + $this->assertSame( $revisionId, $entityRevision->getRevisionId() ); + } + } -- To view, visit https://gerrit.wikimedia.org/r/302199 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ic9ef30431f8ad23f70d84df8fe005d05e4fec9ab Gerrit-PatchSet: 9 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Hoo man <h...@online.de> Gerrit-Reviewer: Aaron Schulz <asch...@wikimedia.org> Gerrit-Reviewer: Aude <aude.w...@gmail.com> Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de> Gerrit-Reviewer: Hoo man <h...@online.de> Gerrit-Reviewer: Smalyshev <smalys...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits