Current File : /home/jvzmxxx/wiki/extensions/Wikibase/lib/includes/Store/Sql/WikiPageEntityMetaDataLookup.php
<?php

namespace Wikibase\Lib\Store\Sql;

use DatabaseBase;
use DBAccessBase;
use DBQueryError;
use ResultWrapper;
use stdClass;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\DataModel\Entity\EntityIdParser;
use Wikibase\Lib\Store\EntityRevisionLookup;

/**
 * Service for looking up meta data about one or more entities as needed for
 * loading entities from WikiPages (via Revision) or to verify an entity against
 * page.page_latest.
 *
 * @since 0.5
 *
 * @license GPL-2.0+
 * @author Daniel Kinzler
 * @author Marius Hoch < hoo@online.de >
 */
class WikiPageEntityMetaDataLookup extends DBAccessBase implements WikiPageEntityMetaDataAccessor {

	/**
	 * @var EntityIdParser
	 */
	private $entityIdParser;

	/**
	 * @param EntityIdParser $entityIdParser
	 * @param string|bool $wiki The name of the wiki database to use (use false for the local wiki)
	 */
	public function __construct(
		EntityIdParser $entityIdParser,
		$wiki = false
	) {
		parent::__construct( $wiki );

		// TODO: migrate table away from using a numeric field so we no longer need this!
		$this->entityIdParser = $entityIdParser;
	}

	/**
	 * @param EntityId[] $entityIds
	 * @param string $mode (EntityRevisionLookup::LATEST_FROM_SLAVE or EntityRevisionLookup::LATEST_FROM_MASTER)
	 *
	 * @throws DBQueryError
	 * @return stdClass[] Array of entity id serialization => object.
	 */
	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 ( $loadFromMaster ) {
			$rows = array_merge(
				$rows,
				$this->selectRevisionInformationMultiple( $loadFromMaster, DB_MASTER )
			);
		}

		return $rows;
	}

	/**
	 * @param EntityId $entityId
	 * @param int $revisionId
	 *
	 * @throws DBQueryError
	 * @return stdClass|bool
	 */
	public function loadRevisionInformationByRevisionId( EntityId $entityId, $revisionId ) {
		$row = $this->selectRevisionInformationById( $entityId, $revisionId, DB_SLAVE );

		if ( !$row ) {
			// Try loading from master
			wfDebugLog( __CLASS__, __FUNCTION__ . ': try to load ' . $entityId
				. " with $revisionId from DB_MASTER." );

			$row = $this->selectRevisionInformationById( $entityId, $revisionId, DB_MASTER );
		}

		return $row;
	}

	/**
	 * Fields we need to select to load a revision
	 *
	 * @return string[]
	 */
	private function selectFields() {
		return array(
			'rev_id',
			'rev_content_format',
			'rev_timestamp',
			'page_latest',
			'page_is_redirect',
			'old_id',
			'old_text',
			'old_flags'
		);
	}

	/**
	 * Selects revision information from the page, revision, and text tables.
	 *
	 * @param EntityId $entityId The entity to query the DB for.
	 * @param int $revisionId The desired revision id
	 * @param int $connType DB_SLAVE or DB_MASTER
	 *
	 * @throws DBQueryError If the query fails.
	 * @return stdClass|bool a raw database row object, or false if no such entity revision exists.
	 */
	private function selectRevisionInformationById( EntityId $entityId, $revisionId, $connType ) {
		$db = $this->getConnection( $connType );

		$join = array();

		// pick text via rev_text_id
		$join['text'] = array( 'INNER JOIN', 'old_id=rev_text_id' );
		$join['page'] = array( 'INNER JOIN', 'rev_page=page_id' );

		wfDebugLog( __CLASS__, __FUNCTION__ . ": Looking up revision $revisionId of " . $entityId );

		$row = $db->selectRow(
			array( 'revision', 'text', 'page' ),
			$this->selectFields(),
			array( 'rev_id' => $revisionId ),
			__METHOD__,
			array(),
			$join
		);

		$this->releaseConnection( $db );

		return $row;
	}

	/**
	 * Selects revision information from the page, revision, and text tables.
	 * Returns an array like entityid -> object or false (if not found).
	 *
	 * @param EntityId[] $entityIds The entities to query the DB for.
	 * @param int $connType DB_SLAVE or DB_MASTER
	 *
	 * @throws DBQueryError If the query fails.
	 * @return stdClass[] Array of entity id serialization => object.
	 */
	private function selectRevisionInformationMultiple( array $entityIds, $connType ) {
		$db = $this->getConnection( $connType );

		$join = array();
		$fields = $this->selectFields();
		// To be able to link rows with entity ids
		$fields[] = 'epp_entity_id';
		$fields[] = 'epp_entity_type';

		$tables = array( 'page', 'revision', 'text', 'wb_entity_per_page' );

		// pick page via epp_page_id
		$join['page'] = array( 'INNER JOIN', 'epp_page_id=page_id' );

		// pick latest revision via page_latest
		$join['revision'] = array( 'INNER JOIN', 'page_latest=rev_id' );

		// pick text via rev_text_id
		$join['text'] = array( 'INNER JOIN', 'old_id=rev_text_id' );

		$res = $db->select( $tables, $fields, $this->getEppWhere( $entityIds, $db ), __METHOD__, array(), $join );

		$this->releaseConnection( $db );

		return $this->indexResultByEntityId( $entityIds, $res );
	}

	/**
	 * Takes a ResultWrapper and indexes the returned rows based on the serialized
	 * entity id of the entities they refer to.
	 *
	 * @param EntityId[] $entityIds
	 * @param ResultWrapper $res
	 *
	 * @return stdClass[] Array of entity id serialization => object.
	 */
	private function indexResultByEntityId( array $entityIds, ResultWrapper $res ) {
		$rows = array();
		// Create a key based map from the rows just returned to reduce
		// the complexity below.
		foreach ( $res as $row ) {
			$rows[$row->epp_entity_type . $row->epp_entity_id] = $row;
		}

		$result = array();
		foreach ( $entityIds as $entityId ) {
			$result[$entityId->getSerialization()] = false;

			// FIXME: this will fail for IDs that do not have a numeric form
			$key = $entityId->getEntityType() . $entityId->getNumericId();
			if ( isset( $rows[$key] ) ) {
				$result[$entityId->getSerialization()] = $rows[$key];
			}
		}

		return $result;
	}

	/**
	 * @param EntityId[] $entityIds
	 * @param DatabaseBase $db
	 *
	 * @return string
	 */
	private function getEppWhere( array $entityIds, DatabaseBase $db ) {
		$where = array();

		foreach ( $entityIds as &$entityId ) {
			$where[] = $db->makeList( array(
				// FIXME: this will fail for IDs that do not have a numeric form
				// Note: if epp_entity_id is quoted the wrong index will be used
				//       thus we cast it to int and leave it unquoted
				'epp_entity_id = ' . (int)$entityId->getNumericId(),
				'epp_entity_type' => $entityId->getEntityType()
			), LIST_AND );
		}

		return $db->makeList( $where, LIST_OR );
	}

}