Page MenuHomePhabricator

Provide EntityRevisionLookup for federated properties
Closed, DeclinedPublic

Description

Currently, federated properties (and probably API entity sources in general?) don’t implement a working EntityRevisionLookup; if you call WikibaseRepo’s EntityRevisionLookup with a federated property ID, it will try to use the standard implementation for local entities (using WikiPageEntityMetaDataLookup and EntityIdLocalPartPageTableEntityQuery) and ultimately crash when calling the unsupported FederatedPropertyId::getLocalPart() method. We should add a working implementation of this service.

As far as I can tell, there’s no conceptual reason why this shouldn’t be possible. The entity storage documentation already makes it clear that “the revision ID is considered to be unique only relative to a given entity ID, not globally”; and while it doesn’t explicitly state that the revision ID doesn’t necessarily refer to the local wiki (and it might make sense to add this to the docs), that’s already the case today: you can use a Commons EntityRevisionLookup with an item ID, and get back a Wikidata revision:

lucaswerkmeister-wmde@mwdebug1002:~$ mwscript shell.php commonswiki
Psy Shell v0.11.5 (PHP 7.2.34-18+0~20210223.60+debian10~1.gbpb21322+wmf5 — cli) by Justin Hileman
>>> $erl = wbr::getEntityRevisionLookup()
=> Wikibase\Lib\Store\CachingEntityRevisionLookup {#5927}

>>> $eip = wbr::getEntityIdParser()
=> Wikibase\DataModel\Entity\DispatchingEntityIdParser {#6194}

>>> $erl->getEntityRevision( $eip->parse( 'Q4115189' ) )->getRevisionId()
=> 1674738524

Event Timeline

This is necessary for T312223, which in turn is necessary for T305032. Currently, the LabelDescriptionLookup implementation that resolves redirects is CachingFallbackLabelDescriptionLookup, and it resolves redirects as part of getting the latest revision ID (to be used for the cache key); so if we want to use this implementation more widely, it must be possible to get the latest revision ID for a federated property. (Practically speaking, a Federated Properties integration test failed when we tried to use CachingFallbackLabelDescriptionLookup in the relevant place, see Gerrit change and build.)

Federated Properties isn’t as complete as I thought; this might not be feasible at this time.

Instead, I think we can fulfill the requirements of T312223 with another FallbackLabelDescriptionLookup implementation, which wraps both a LanguageFallbackLabelDescriptionLookup and a CachingFallbackLabelDescriptionLookup (not necessarily requiring these interfaces), and forwards most calls to the Caching one, but requests for federated property IDs to the Language one. The factory would then return this new class, so that both the factory and its output can be used with any entity type, even if federated properties don’t have a working entity revision lookup yet. (This would lose caching for federated properties, but that should be okay; properties can’t be redirects anyways, so that part wouldn’t matter.)

Later, this implementation could be removed once there is a working EntityRevisionLookup for federated properties. (It wouldn’t need to support looking up arbitrary entity IDs, only getLatestRevisionId() in fact.)

Just for fun before I delete the file – here’s the ApiEntityRevisionLookup implementation I drafted up (totally untested and not wired up in WikibaseRepo.FederatedProperties.OverrideEntityServices.php yet):

<?php

declare( strict_types = 1 );

namespace Wikibase\Repo\FederatedProperties;

use Deserializers\Deserializer;
use MWTimestamp;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\Lib\FederatedProperties\FederatedPropertyId;
use Wikibase\Lib\Store\EntityRevision;
use Wikibase\Lib\Store\EntityRevisionLookup;
use Wikibase\Lib\Store\LookupConstants;
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
use Wikimedia\Assert\Assert;

/** @license GPL-2.0-or-later */
class ApiEntityRevisionLookup implements EntityRevisionLookup {

	/** @var ApiEntityLookup */
	private $apiEntityLookup;

	/** @var Deserializer */
	private $entityDeserializer;

	public function __construct(
		ApiEntityLookup $apiEntityLookup,
		Deserializer $entityDeserializer
	) {
		$this->apiEntityLookup = $apiEntityLookup;
		$this->entityDeserializer = $entityDeserializer;
	}

	public function getEntityRevision(
		EntityId $entityId,
		$revisionId = 0,
		$mode = LookupConstants::LATEST_FROM_REPLICA
	) {
		Assert::parameterType( FederatedPropertyId::class, $entityId, '$entityId' );
		Assert::parameter( $revisionId === 0, '$revisionId', 'must be 0 (latest)' );
		/** @var FederatedPropertyId $entityId */
		'@phan-var FederatedPropertyId $entityId';

		$this->apiEntityLookup->fetchEntities( [ $entityId ] );
		$data = $this->apiEntityLookup->getResultPartForId( $entityId );
		if ( isset( $data['missing'] ) ) {
			return null;
		}

		$revisionId = $data['lastrevid'];
		$modified = MWTimestamp::convert( TS_MW, $data['modified'] );
		if ( $data['id'] !== $entityId->getRemoteIdSerialization() ) {
			throw new RevisionedUnresolvedRedirectException(
				$entityId,
				$data['id'], // TODO parse?
				$revisionId,
				$modified
			);
		}

		$entity = $this->entityDeserializer->deserialize( $data );
		return new EntityRevision( $entity, $revisionId, $modified );
	}

	public function getLatestRevisionId( EntityId $entityId, $mode = LookupConstants::LATEST_FROM_REPLICA ) {
		// TODO: Implement getLatestRevisionId() method.
	}

}