summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_Sql3SmwIds.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_Sql3SmwIds.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_Sql3SmwIds.php1296
1 files changed, 1296 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_Sql3SmwIds.php b/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_Sql3SmwIds.php
new file mode 100644
index 00000000..b8c402b0
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_Sql3SmwIds.php
@@ -0,0 +1,1296 @@
+<?php
+
+use SMW\DIProperty;
+use SMW\DIWikiPage;
+use SMW\MediaWiki\Collator;
+use SMW\PropertyRegistry;
+use SMW\RequestOptions;
+use SMW\SQLStore\EntityStore\IdCacheManager;
+use SMW\SQLStore\IdToDataItemMatchFinder;
+use SMW\SQLStore\RedirectStore;
+use SMW\SQLStore\SQLStore;
+use SMW\SQLStore\SQLStoreFactory;
+use SMW\SQLStore\TableFieldUpdater;
+use SMWDataItem as DataItem;
+use SMW\MediaWiki\Jobs\UpdateJob;
+use SMW\MediaWiki\Connection\Sequence;
+use SMW\TypesRegistry;
+
+/**
+ * @ingroup SMWStore
+ * @since 1.8
+ * @author Markus Krötzsch
+ */
+
+/**
+ * Class to access the SMW IDs table in SQLStore3.
+ * Provides transparent in-memory caching facilities.
+ *
+ * Documentation for the SMW IDs table: This table is a dictionary that
+ * assigns integer IDs to pages, properties, and other objects used by SMW.
+ * All tables that refer to such objects store these IDs instead. If the ID
+ * information is lost (e.g., table gets deleted), then the data stored in SMW
+ * is no longer meaningful: all tables need to be dropped, recreated, and
+ * refreshed to get back to a working database.
+ *
+ * The table has a column for storing interwiki prefixes, used to refer to
+ * pages on external sites (like in MediaWiki). This column is also used to
+ * mark some special objects in the table, using "interwiki prefixes" that
+ * cannot occur in MediaWiki:
+ *
+ * - Rows with iw SMW_SQL3_SMWREDIIW are similar to normal entries for
+ * (internal) wiki pages, but the iw indicates that the page is a redirect, the
+ * (target of which should be sought using the smw_fpt_redi table.
+ *
+ * - The (unique) row with iw SMW_SQL3_SMWBORDERIW just marks the border
+ * between predefined ids (rows that are reserved for hardcoded ids built into
+ * SMW) and normal entries. It is no object, but makes sure that SQL's auto
+ * increment counter is high enough to not add any objects before that marked
+ * "border".
+ *
+ * @note Do not call the constructor of SMWDIWikiPage using data from the SMW
+ * IDs table; use SMWDIHandlerWikiPage::dataItemFromDBKeys() instead. The table
+ * does not always contain data as required wiki pages. Especially predefined
+ * properties are represented by language-independent keys rather than proper
+ * titles. SMWDIHandlerWikiPage takes care of this.
+ *
+ * @since 1.8
+ *
+ * @ingroup SMWStore
+ */
+class SMWSql3SmwIds {
+
+ /**
+ * Specifies the border limit for pre-defined properties declared
+ * in SMWSql3SmwIds::special_ids
+ */
+ const FXD_PROP_BORDER_ID = SMWSQLStore3::FIXED_PROPERTY_ID_UPPERBOUND;
+
+ /**
+ * Name of the table to store IDs in.
+ *
+ * @note This should never change. Existing wikis will have to drop and
+ * rebuild their SMW tables completely to recover from any change here.
+ */
+ const TABLE_NAME = SMWSQLStore3::ID_TABLE;
+
+ const MAX_CACHE_SIZE = 1000;
+ const POOLCACHE_ID = 'smw.sqlstore';
+
+ /**
+ * Parent SMWSQLStore3.
+ *
+ * @since 1.8
+ * @var SMWSQLStore3
+ */
+ public $store;
+
+ /**
+ * @var SQLStoreFactory
+ */
+ private $factory;
+
+ /**
+ * @var IdToDataItemMatchFinder
+ */
+ private $idMatchFinder;
+
+ /**
+ * @var RedirectStore
+ */
+ private $redirectStore;
+
+ /**
+ * @var TableFieldUpdater
+ */
+ private $tableFieldUpdater;
+
+ /**
+ * @var array
+ */
+ public static $special_ids = [];
+
+ /**
+ * @var IdCacheManager
+ */
+ private $idCacheManager;
+
+ /**
+ * @var IdEntityFinder
+ */
+ private $idEntityFinder;
+
+ /**
+ * @var IdChanger
+ */
+ private $idChanger;
+
+ /**
+ * @var UniquenessLookup
+ */
+ private $uniquenessLookup;
+
+ /**
+ * @since 1.8
+ * @param SMWSQLStore3 $store
+ */
+ public function __construct( SMWSQLStore3 $store, SQLStoreFactory $factory ) {
+ $this->store = $store;
+ $this->factory = $factory;
+ $this->initCache();
+
+ $this->idEntityFinder = $this->factory->newIdEntityFinder(
+ $this->idCacheManager
+ );
+
+ $this->redirectStore = $this->factory->newRedirectStore();
+ $this->uniquenessLookup = $this->factory->newUniquenessLookup();
+
+ $this->tableFieldUpdater = $this->factory->newTableFieldUpdater();
+ $this->idChanger = $this->factory->newIdChanger();
+
+ self::$special_ids = TypesRegistry::getFixedPropertyIdList();
+ }
+
+ /**
+ * @since 2.1
+ *
+ * @param DIWikiPage $subject
+ *
+ * @return boolean
+ */
+ public function isRedirect( DIWikiPage $subject ) {
+ return $this->redirectStore->isRedirect( $subject->getDBKey(), $subject->getNamespace() );
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param DataItem $dataItem
+ *
+ * @return boolean
+ */
+ public function isUnique( DataItem $dataItem ) {
+ return $this->uniquenessLookup->isUnique( $dataItem );
+ }
+
+ /**
+ * @see RedirectStore::findRedirect
+ *
+ * @since 2.1
+ *
+ * @param string $title DB key
+ * @param integer $namespace
+ *
+ * @return integer
+ */
+ public function findRedirect( $title, $namespace ) {
+ return $this->redirectStore->findRedirect( $title, $namespace );
+ }
+
+ /**
+ * @see RedirectStore::addRedirect
+ *
+ * @since 2.1
+ *
+ * @param integer $id
+ * @param string $title
+ * @param integer $namespace
+ */
+ public function addRedirect( $id, $title, $namespace ) {
+ $this->redirectStore->addRedirect( $id, $title, $namespace );
+ }
+
+ /**
+ * @see RedirectStore::updateRedirect
+ *
+ * @since 3.0
+ *
+ * @param integer $id
+ * @param string $title
+ * @param integer $namespace
+ */
+ public function updateRedirect( $id, $title, $namespace ) {
+ $this->redirectStore->updateRedirect( $id, $title, $namespace );
+ }
+
+ /**
+ * @see RedirectStore::deleteRedirect
+ *
+ * @since 2.1
+ *
+ * @param string $title
+ * @param integer $namespace
+ */
+ public function deleteRedirect( $title, $namespace ) {
+ $this->redirectStore->deleteRedirect( $title, $namespace );
+ }
+
+ /**
+ * Find the numeric ID used for the page of the given title,
+ * namespace, interwiki, and subobject. If $canonical is set to true,
+ * redirects are taken into account to find the canonical alias ID for
+ * the given page. If no such ID exists, 0 is returned. The Call-By-Ref
+ * parameter $sortkey is set to the current sortkey, or to '' if no ID
+ * exists.
+ *
+ * If $fetchhashes is true, the property table hash blob will be
+ * retrieved in passing if the opportunity arises, and cached
+ * internally. This will speed up a subsequent call to
+ * getPropertyTableHashes() for this id. This should only be done
+ * if such a call is intended, both to safe the previous cache and
+ * to avoid extra work (even if only a little) to fill it.
+ *
+ * @since 1.8
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string $iw interwiki prefix
+ * @param string $subobjectName name of subobject
+ * @param string $sortkey call-by-ref will be set to sortkey
+ * @param boolean $canonical should redirects be resolved?
+ * @param boolean $fetchHashes should the property hashes be obtained and cached?
+ * @return integer SMW id or 0 if there is none
+ */
+ public function getSMWPageIDandSort( $title, $namespace, $iw, $subobjectName, &$sortkey, $canonical, $fetchHashes = false ) {
+ $id = $this->getPredefinedData( $title, $namespace, $iw, $subobjectName, $sortkey );
+ if ( $id != 0 ) {
+ return (int)$id;
+ } else {
+ return (int)$this->getDatabaseIdAndSort( $title, $namespace, $iw, $subobjectName, $sortkey, $canonical, $fetchHashes );
+ }
+ }
+
+ /**
+ * Find the numeric ID used for the page of the given normalized title,
+ * namespace, interwiki, and subobjectName. Predefined IDs are not
+ * taken into account (however, they would still be found correctly by
+ * an avoidable database read if they are stored correctly in the
+ * database; this should always be the case). In all other aspects, the
+ * method works just like getSMWPageIDandSort().
+ *
+ * @since 1.8
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string $iw interwiki prefix
+ * @param string $subobjectName name of subobject
+ * @param string $sortkey call-by-ref will be set to sortkey
+ * @param boolean $canonical should redirects be resolved?
+ * @param boolean $fetchHashes should the property hashes be obtained and cached?
+ * @return integer SMW id or 0 if there is none
+ */
+ protected function getDatabaseIdAndSort( $title, $namespace, $iw, $subobjectName, &$sortkey, $canonical, $fetchHashes ) {
+ global $smwgQEqualitySupport;
+
+ $db = $this->store->getConnection( 'mw.db' );
+
+ // Integration test "query-04-02-subproperty-dc-import-marc21.json"
+ // showed a deterministic failure (due to a wrong cache id during querying
+ // for redirects) hence we force to read directly from the RedirectStore
+ // for objects marked as redirect
+ if ( $iw === SMW_SQL3_SMWREDIIW && $canonical &&
+ $smwgQEqualitySupport !== SMW_EQ_NONE && $subobjectName === '' ) {
+ $id = $this->findRedirect( $title, $namespace );
+ } else {
+ $id = $this->idCacheManager->getId( [ $title, (int)$namespace, $iw, $subobjectName ] );
+ }
+
+ if ( $id !== false && $id != 0 ) { // cache hit
+ $sortkey = $this->idCacheManager->getSort( [ $title, (int)$namespace, $iw, $subobjectName ] );
+ } elseif ( $iw == SMW_SQL3_SMWREDIIW && $canonical &&
+ $smwgQEqualitySupport != SMW_EQ_NONE && $subobjectName === '' ) {
+ $id = $this->findRedirect( $title, $namespace );
+ if ( $id != 0 ) {
+
+ if ( $fetchHashes ) {
+ $select = [ 'smw_sortkey', 'smw_sort', 'smw_proptable_hash' ];
+ } else {
+ $select = [ 'smw_sortkey', 'smw_sort' ];
+ }
+
+ $row = $db->selectRow(
+ self::TABLE_NAME,
+ $select,
+ [ 'smw_id' => $id ],
+ __METHOD__
+ );
+
+ if ( $row !== false ) {
+ // Make sure that smw_sort is being re-computed in case it is null
+ $sortkey = $row->smw_sort === null ? '' : $row->smw_sortkey;
+ if ( $fetchHashes ) {
+ $this->setPropertyTableHashesCache( $id, $row->smw_proptable_hash );
+ }
+ } else { // inconsistent DB; just recover somehow
+ $sortkey = str_replace( '_', ' ', $title );
+ }
+ } else {
+ $sortkey = '';
+ }
+ $this->setCache( $title, $namespace, $iw, $subobjectName, $id, $sortkey );
+ } else {
+
+ if ( $fetchHashes ) {
+ $select = [ 'smw_id', 'smw_sortkey', 'smw_sort', 'smw_proptable_hash' ];
+ } else {
+ $select = [ 'smw_id', 'smw_sortkey', 'smw_sort' ];
+ }
+
+ // #2001
+ // In cases where title components are excessively long (beyond the
+ // field limit) it has been observed that at least on MySQL/MariaDB no
+ // appropriate matches are found even though a row with a truncated
+ // representation exists in the table.
+ //
+ // `postgres` has no field limit and a divergent behaviour has not
+ // been observed
+ if ( $subobjectName !== '' && !$db->isType( 'postgres' ) ) {
+ $subobjectName = mb_substr( $subobjectName, 0, 255 );
+ }
+
+ $row = $db->selectRow(
+ self::TABLE_NAME,
+ $select,
+ [
+ 'smw_title' => $title,
+ 'smw_namespace' => $namespace,
+ 'smw_iw' => $iw,
+ 'smw_subobject' => $subobjectName
+ ],
+ __METHOD__
+ );
+
+ //$this->selectrow_sort_debug++;
+
+ if ( $row !== false ) {
+ $id = $row->smw_id;
+ // Make sure that smw_sort is being re-computed in case it is null
+ $sortkey = $row->smw_sort === null ? '' : $row->smw_sortkey;
+ if ( $fetchHashes ) {
+ $this->setPropertyTableHashesCache( $id, $row->smw_proptable_hash);
+ }
+ } else {
+ $id = 0;
+ $sortkey = '';
+ }
+
+ $this->setCache(
+ $title,
+ $namespace,
+ $iw,
+ $subobjectName,
+ $id,
+ $sortkey
+ );
+ }
+
+ if ( $id == 0 && $subobjectName === '' && $iw === '' ) { // could be a redirect; check
+ $id = $this->getSMWPageIDandSort(
+ $title,
+ $namespace,
+ SMW_SQL3_SMWREDIIW,
+ $subobjectName,
+ $sortkey,
+ $canonical,
+ $fetchHashes
+ );
+ }
+
+ return $id;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @return []
+ */
+ public function findDuplicates() {
+ return $this->uniquenessLookup->findDuplicates();
+ }
+
+ /**
+ * @since 2.3
+ *
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string|null $iw interwiki prefix
+ * @param string $subobjectName name of subobject
+ *
+ * @param array
+ */
+ public function findAllEntitiesThatMatch( $title, $namespace, $iw = null, $subobjectName = '' ) {
+
+ $matches = [];
+ $query = [];
+
+ $query['fields'] = ['smw_id'];
+
+ $query['conditions'] = [
+ 'smw_title' => $title,
+ 'smw_namespace' => $namespace,
+ 'smw_iw' => $iw,
+ 'smw_subobject' => $subobjectName
+ ];
+
+ // Null means select all (incl. those marked delete, redi etc.)
+ if ( $iw === null ) {
+ unset( $query['conditions']['smw_iw'] );
+ }
+
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ $rows = $connection->select(
+ $connection->tableName( self::TABLE_NAME ),
+ $query['fields'],
+ $query['conditions'],
+ __METHOD__
+ );
+
+ if ( $rows === false ) {
+ return $matches;
+ }
+
+ foreach ( $rows as $row ) {
+ $matches[] = (int)$row->smw_id;
+ }
+
+ return $matches;
+ }
+
+ /**
+ * @since 2.4
+ *
+ * @param DIWikiPage $subject
+ *
+ * @param boolean
+ */
+ public function exists( DIWikiPage $subject ) {
+ return $this->getId( $subject ) > 0;
+ }
+
+ /**
+ * @note SMWSql3SmwIds::getSMWPageID has some issues with the cache as it returned
+ * 0 even though an object was matchable, using this method is safer then trying
+ * to encipher getSMWPageID related methods.
+ *
+ * It uses the PoolCache which means Lru is in place to avoid memory leakage.
+ *
+ * @since 2.4
+ *
+ * @param DIWikiPage $subject
+ *
+ * @param integer
+ */
+ public function getId( DIWikiPage $subject ) {
+
+ // Try to match a predefined property
+ if ( $subject->getNamespace() === SMW_NS_PROPERTY && $subject->getInterWiki() === '' ) {
+ $property = DIProperty::newFromUserLabel( $subject->getDBKey() );
+ $key = $property->getKey();
+
+ // Has a fixed ID?
+ if ( isset( self::$special_ids[$key] ) && $subject->getSubobjectName() === '' ) {
+ return self::$special_ids[$key];
+ }
+
+ // Switch title for fixed properties without a fixed ID (e.g. _MIME is the smw_title)
+ if ( !$property->isUserDefined() ) {
+ $subject = new DIWikiPage(
+ $key,
+ SMW_NS_PROPERTY,
+ $subject->getInterWiki(),
+ $subject->getSubobjectName()
+ );
+ }
+ }
+
+ if ( ( $id = $this->idCacheManager->getId( $subject ) ) !== false ) {
+ return $id;
+ }
+
+ $id = 0;
+
+ $row = $this->store->getConnection( 'mw.db' )->selectRow(
+ self::TABLE_NAME,
+ [ 'smw_id' ],
+ [
+ 'smw_title' => $subject->getDBKey(),
+ 'smw_namespace' => $subject->getNamespace(),
+ 'smw_iw' => $subject->getInterWiki(),
+ 'smw_subobject' => $subject->getSubobjectName()
+ ],
+ __METHOD__
+ );
+
+ if ( $row !== false ) {
+ $id = $row->smw_id;
+
+ // Legacy
+ $this->setCache(
+ $subject->getDBKey(),
+ $subject->getNamespace(),
+ $subject->getInterWiki(),
+ $subject->getSubobjectName(),
+ $id,
+ $subject->getSortKey()
+ );
+ }
+
+ return $id;
+ }
+
+ /**
+ * Convenience method for calling getSMWPageIDandSort without
+ * specifying a sortkey (if not asked for).
+ *
+ * @since 1.8
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string $iw interwiki prefix
+ * @param string $subobjectName name of subobject
+ * @param boolean $canonical should redirects be resolved?
+ * @param boolean $fetchHashes should the property hashes be obtained and cached?
+ * @return integer SMW id or 0 if there is none
+ */
+ public function getSMWPageID( $title, $namespace, $iw, $subobjectName, $canonical = true, $fetchHashes = false ) {
+ $sort = '';
+ return $this->getSMWPageIDandSort( $title, $namespace, $iw, $subobjectName, $sort, $canonical, $fetchHashes );
+ }
+
+ /**
+ * Find the numeric ID used for the page of the given title, namespace,
+ * interwiki, and subobjectName. If $canonical is set to true,
+ * redirects are taken into account to find the canonical alias ID for
+ * the given page. If no such ID exists, a new ID is created and
+ * returned. In any case, the current sortkey is set to the given one
+ * unless $sortkey is empty.
+ *
+ * @note Using this with $canonical==false can make sense, especially when
+ * the title is a redirect target (we do not want chains of redirects).
+ * But it is of no relevance if the title does not have an id yet.
+ *
+ * @since 1.8
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string $iw interwiki prefix
+ * @param string $subobjectName name of subobject
+ * @param boolean $canonical should redirects be resolved?
+ * @param string $sortkey call-by-ref will be set to sortkey
+ * @param boolean $fetchHashes should the property hashes be obtained and cached?
+ * @return integer SMW id or 0 if there is none
+ */
+ public function makeSMWPageID( $title, $namespace, $iw, $subobjectName, $canonical = true, $sortkey = '', $fetchHashes = false ) {
+ $id = $this->getPredefinedData( $title, $namespace, $iw, $subobjectName, $sortkey );
+ if ( $id != 0 ) {
+ return (int)$id;
+ } else {
+ return (int)$this->makeDatabaseId( $title, $namespace, $iw, $subobjectName, $canonical, $sortkey, $fetchHashes );
+ }
+ }
+
+ /**
+ * Find the numeric ID used for the page of the given normalized title,
+ * namespace, interwiki, and subobjectName. Predefined IDs are not
+ * taken into account (however, they would still be found correctly by
+ * an avoidable database read if they are stored correctly in the
+ * database; this should always be the case). In all other aspects, the
+ * method works just like makeSMWPageID(). Especially, if no ID exists,
+ * a new ID is created and returned.
+ *
+ * @since 1.8
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string $iw interwiki prefix
+ * @param string $subobjectName name of subobject
+ * @param boolean $canonical should redirects be resolved?
+ * @param string $sortkey call-by-ref will be set to sortkey
+ * @param boolean $fetchHashes should the property hashes be obtained and cached?
+ * @return integer SMW id or 0 if there is none
+ */
+ protected function makeDatabaseId( $title, $namespace, $iw, $subobjectName, $canonical, $sortkey, $fetchHashes ) {
+
+ $oldsort = '';
+ $id = $this->getDatabaseIdAndSort( $title, $namespace, $iw, $subobjectName, $oldsort, $canonical, $fetchHashes );
+ $db = $this->store->getConnection( 'mw.db' );
+ $collator = Collator::singleton();
+
+ // Safeguard to ensure that no duplicate IDs are created
+ if ( $id == 0 ) {
+ $id = $this->getId( new DIWikiPage( $title, $namespace, $iw, $subobjectName ) );
+ }
+
+ $db->beginAtomicTransaction( __METHOD__ );
+
+ if ( $id == 0 ) {
+ $sortkey = $sortkey ? $sortkey : ( str_replace( '_', ' ', $title ) );
+
+ // Bug 42659
+ $sequenceValue = $db->nextSequenceValue(
+ Sequence::makeSequence( SQLStore::ID_TABLE, 'smw_id' )
+ );
+
+ // #2089 (MySQL 5.7 complained with "Data too long for column")
+ $sortkey = mb_substr( $sortkey, 0, 254 );
+
+ $db->insert(
+ self::TABLE_NAME,
+ [
+ 'smw_id' => $sequenceValue,
+ 'smw_title' => $title,
+ 'smw_namespace' => $namespace,
+ 'smw_iw' => $iw,
+ 'smw_subobject' => $subobjectName,
+ 'smw_sortkey' => $sortkey,
+ 'smw_sort' => $collator->getSortKey( $sortkey ),
+ 'smw_hash' => $this->computeSha1( [ $title, (int)$namespace, $iw, $subobjectName ] )
+ ],
+ __METHOD__
+ );
+
+ $id = (int)$db->insertId();
+
+ // Properties also need to be in the property statistics table
+ if( $namespace === SMW_NS_PROPERTY ) {
+
+ $propertyStatisticsStore = $this->factory->newPropertyStatisticsStore(
+ $db
+ );
+
+ $propertyStatisticsStore->insertUsageCount( $id, 0 );
+ }
+
+ $this->setCache( $title, $namespace, $iw, $subobjectName, $id, $sortkey );
+
+ if ( $fetchHashes ) {
+ $this->setPropertyTableHashesCache( $id, null );
+ }
+
+ } elseif ( $sortkey !== '' && ( $sortkey != $oldsort || !$collator->isIdentical( $oldsort, $sortkey ) ) ) {
+ $this->tableFieldUpdater->updateSortField( $id, $sortkey );
+ $this->setCache( $title, $namespace, $iw, $subobjectName, $id, $sortkey );
+ }
+
+ $db->endAtomicTransaction( __METHOD__ );
+
+ return $id;
+ }
+
+ /**
+ * Properties have a mechanisms for being predefined (i.e. in PHP instead
+ * of in wiki). Special "interwiki" prefixes separate the ids of such
+ * predefined properties from the ids for the current pages (which may,
+ * e.g., be moved, while the predefined object is not movable).
+ *
+ * @todo This documentation is out of date. Right now, the special
+ * interwiki is used only for special properties without a label, i.e.,
+ * which cannot be shown to a user. This allows us to filter such cases
+ * from all queries that retrieve lists of properties. It should be
+ * checked that this is really the only use that this has throughout
+ * the code.
+ *
+ * @since 1.8
+ * @param SMWDIProperty $property
+ * @return string
+ */
+ public function getPropertyInterwiki( SMWDIProperty $property ) {
+ return ( $property->getLabel() !== '' ) ? '' : SMW_SQL3_SMWINTDEFIW;
+ }
+
+ /**
+ * @since 2.1
+ *
+ * @param integer $sid
+ * @param DIWikiPage $subject
+ * @param integer|string|null $interwiki
+ */
+ public function updateInterwikiField( $sid, DIWikiPage $subject, $interwiki = null ) {
+
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ if ( $interwiki === null ) {
+ $interwiki = $subject->getInterWiki();
+ }
+
+ $hash = [
+ $subject->getDBKey(),
+ (int)$subject->getNamespace(),
+ $interwiki,
+ $subject->getSubobjectName()
+ ];
+
+ $connection->update(
+ self::TABLE_NAME,
+ [
+ 'smw_iw' => $interwiki,
+ 'smw_hash' => $this->computeSha1( $hash )
+ ],
+ [ 'smw_id' => $sid ],
+ __METHOD__
+ );
+
+ $this->setCache(
+ $subject->getDBKey(),
+ $subject->getNamespace(),
+ $subject->getInterWiki(),
+ $subject->getSubobjectName(),
+ $sid,
+ $subject->getSortKey()
+ );
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param string $title
+ * @param integer $namespace
+ * @param string $iw
+ */
+ public function findAssociatedRev( $title, $namespace, $iw = '' ) {
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ $row = $connection->selectRow(
+ self::TABLE_NAME,
+ 'smw_rev',
+ [
+ "smw_title =" . $connection->addQuotes( $title ),
+ "smw_namespace =" . $connection->addQuotes( $namespace ),
+ "smw_iw =" . $connection->addQuotes( $iw ),
+ "smw_subobject =''"
+ ],
+ __METHOD__
+ );
+
+ return $row === false ? 0 : $row->smw_rev;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param integer $sid
+ * @param integer $sid
+ */
+ public function updateRevField( $sid, $rev_id ) {
+ $this->tableFieldUpdater->updateRevField( $sid, $rev_id );
+ }
+
+ /**
+ * Fetch the ID for an SMWDIProperty object. This method achieves the
+ * same as getSMWPageID(), but avoids additional normalization steps
+ * that have already been performed when creating an SMWDIProperty
+ * object.
+ *
+ * @note There is no distinction between properties and inverse
+ * properties here. A property and its inverse have the same ID in SMW.
+ *
+ * @param SMWDIProperty $property
+ * @return integer
+ */
+ public function getSMWPropertyID( SMWDIProperty $property ) {
+ if ( array_key_exists( $property->getKey(), self::$special_ids ) ) {
+ return self::$special_ids[$property->getKey()];
+ } else {
+ $sortkey = '';
+ return $this->getDatabaseIdAndSort( $property->getKey(), SMW_NS_PROPERTY, $this->getPropertyInterwiki( $property ), '', $sortkey, true, false );
+ }
+ }
+
+ /**
+ * Fetch and possibly create the ID for an SMWDIProperty object. The
+ * method achieves the same as getSMWPageID() but avoids additional
+ * normalization steps that have already been performed when creating
+ * an SMWDIProperty object.
+ *
+ * @see getSMWPropertyID
+ * @param SMWDIProperty $property
+ * @return integer
+ */
+ public function makeSMWPropertyID( SMWDIProperty $property ) {
+ if ( array_key_exists( $property->getKey(), self::$special_ids ) ) {
+ return (int)self::$special_ids[$property->getKey()];
+ } else {
+ return (int)$this->makeDatabaseId(
+ $property->getKey(),
+ SMW_NS_PROPERTY,
+ $this->getPropertyInterwiki( $property ),
+ '',
+ true,
+ $property->getLabel(),
+ false
+ );
+ }
+ }
+
+ /**
+ * Normalize the information for an SMW object (page etc.) and return
+ * the predefined ID if any. All parameters are call-by-reference and
+ * will be changed to perform any kind of built-in normalization that
+ * SMW requires. This mainly applies to predefined properties that
+ * should always use their property key as a title, have fixed
+ * sortkeys, etc. Some very special properties also have fixed IDs that
+ * do not require any DB lookups. In such cases, the method returns
+ * this ID; otherwise it returns 0.
+ *
+ * @note This function could be extended to account for further kinds
+ * of normalization and predefined ID. However, both getSMWPropertyID
+ * and makeSMWPropertyID must then also be adjusted to do the same.
+ *
+ * @since 1.8
+ * @param string $title DB key
+ * @param integer $namespace namespace
+ * @param string $iw interwiki prefix
+ * @param string $subobjectName
+ * @param string $sortkey
+ * @return integer predefined id or 0 if none
+ */
+ protected function getPredefinedData( &$title, &$namespace, &$iw, &$subobjectName, &$sortkey ) {
+ if ( $namespace == SMW_NS_PROPERTY &&
+ ( $iw === '' || $iw == SMW_SQL3_SMWINTDEFIW ) && $title != '' ) {
+
+ // Check if this is a predefined property:
+ if ( $title{0} != '_' ) {
+ // This normalization also applies to
+ // subobjects of predefined properties.
+ $newTitle = PropertyRegistry::getInstance()->findPropertyIdByLabel( str_replace( '_', ' ', $title ) );
+ if ( $newTitle ) {
+ $title = $newTitle;
+ $sortkey = PropertyRegistry::getInstance()->findPropertyLabelById( $title );
+ if ( $sortkey === '' ) {
+ $iw = SMW_SQL3_SMWINTDEFIW;
+ }
+ }
+ }
+
+ // Check if this is a property with a fixed SMW ID:
+ if ( $subobjectName === '' && array_key_exists( $title, self::$special_ids ) ) {
+ return self::$special_ids[$title];
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Change an internal id to another value. If no target value is given, the
+ * value is changed to become the last id entry (based on the automatic id
+ * increment of the database). Whatever currently occupies this id will be
+ * moved consistently in all relevant tables. Whatever currently occupies
+ * the target id will be ignored (it should be ensured that nothing is
+ * moved to an id that is still in use somewhere).
+ *
+ * @since 1.8
+ * @param integer $curid
+ * @param integer $targetid
+ */
+ public function moveSMWPageID( $curid, $targetid = 0 ) {
+ $db = $this->store->getConnection();
+
+ $row = $db->selectRow(
+ self::TABLE_NAME,
+ '*',
+ [ 'smw_id' => $curid ],
+ __METHOD__
+ );
+
+ if ( $row === false ) {
+ return; // no id at current position, ignore
+ }
+
+ $db->beginAtomicTransaction( __METHOD__ );
+
+ if ( $targetid == 0 ) { // append new id
+
+ // Bug 42659
+ $sequenceValue = $db->nextSequenceValue(
+ Sequence::makeSequence( SQLStore::ID_TABLE, 'smw_id' )
+ );
+
+ $db->insert(
+ self::TABLE_NAME,
+ [
+ 'smw_id' => $sequenceValue,
+ 'smw_title' => $row->smw_title,
+ 'smw_namespace' => $row->smw_namespace,
+ 'smw_iw' => $row->smw_iw,
+ 'smw_subobject' => $row->smw_subobject,
+ 'smw_sortkey' => $row->smw_sortkey,
+ 'smw_sort' => $row->smw_sort
+ ],
+ __METHOD__
+ );
+
+ $targetid = $db->insertId();
+ } else { // change to given id
+ $db->insert(
+ self::TABLE_NAME,
+ [ 'smw_id' => $targetid,
+ 'smw_title' => $row->smw_title,
+ 'smw_namespace' => $row->smw_namespace,
+ 'smw_iw' => $row->smw_iw,
+ 'smw_subobject' => $row->smw_subobject,
+ 'smw_sortkey' => $row->smw_sortkey,
+ 'smw_sort' => $row->smw_sort
+ ],
+ __METHOD__
+ );
+ }
+
+ $db->delete(
+ self::TABLE_NAME,
+ [
+ 'smw_id' => $curid
+ ],
+ __METHOD__
+ );
+
+ $this->setCache(
+ $row->smw_title,
+ $row->smw_namespace,
+ $row->smw_iw,
+ $row->smw_subobject,
+ $targetid,
+ $row->smw_sortkey
+ );
+
+ $this->idChanger->change(
+ $curid,
+ $targetid,
+ $row->smw_namespace,
+ $row->smw_namespace
+ );
+
+ $db->endAtomicTransaction( __METHOD__ );
+
+ if ( ( $title = \Title::newFromText( $row->smw_title, $row->smw_namespace ) ) !== null ) {
+ $updateJob = new UpdateJob(
+ $title
+ );
+
+ $updateJob->insert();
+ }
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param string|array $args
+ *
+ * @return string
+ */
+ public function computeSha1( $args = '' ) {
+ return IdCacheManager::computeSha1( $args );
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param array $list
+ */
+ public function warmUpCache( $list = [] ) {
+
+ $hashList = [];
+
+ if ( $list instanceof \SMWQueryResult ) {
+ $list = $list->getResults();
+ }
+
+ if ( !$list instanceof \Iterator && !is_array( $list ) ) {
+ return;
+ }
+
+ foreach ( $list as $item ) {
+
+ $hash = null;
+
+ if ( $item instanceof DIWikiPage ) {
+ $hash = [
+ $item->getDBKey(),
+ (int)$item->getNamespace(),
+ $item->getInterwiki(),
+ $item->getSubobjectName()
+ ];
+ }
+
+ if ( $item instanceof DIProperty ) {
+
+ // Avoid _SKEY as it is not used during an entity lookup to
+ // match an ID
+ if ( $item->getKey() === '_SKEY' ) {
+ continue;
+ }
+
+ $hash = [ $item->getKey(), SMW_NS_PROPERTY, '', '' ];
+ }
+
+ if ( $hash === null ) {
+ continue;
+ }
+
+ $hash = IdCacheManager::computeSha1( $hash );
+
+ if ( !$this->idCacheManager->hasCache( $hash ) ) {
+ $hashList[] = $hash;
+ }
+ }
+
+ if ( $hashList === [] ) {
+ return;
+ }
+
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ $rows = $connection->select(
+ SQLStore::ID_TABLE,
+ [
+ 'smw_id',
+ 'smw_title',
+ 'smw_namespace',
+ 'smw_iw',
+ 'smw_subobject',
+ 'smw_sortkey',
+ 'smw_sort'
+ ],
+ [
+ 'smw_hash' => $hashList
+ ],
+ __METHOD__
+ );
+
+ foreach ( $rows as $row ) {
+ $sortkey = $row->smw_sort === null ? '' : $row->smw_sortkey;
+
+ $this->idCacheManager->setCache(
+ $row->smw_title,
+ $row->smw_namespace,
+ $row->smw_iw,
+ $row->smw_subobject,
+ $row->smw_id,
+ $sortkey
+ );
+ }
+ }
+
+ /**
+ * Add or modify a cache entry. The key consists of the
+ * parameters $title, $namespace, $interwiki, and $subobject. The
+ * cached data is $id and $sortkey.
+ *
+ * @since 1.8
+ * @param string $title
+ * @param integer $namespace
+ * @param string $interwiki
+ * @param string $subobject
+ * @param integer $id
+ * @param string $sortkey
+ */
+ public function setCache( $title, $namespace, $interwiki, $subobject, $id, $sortkey ) {
+ $this->idCacheManager->setCache( $title, $namespace, $interwiki, $subobject, $id, $sortkey );
+ }
+
+ /**
+ * @since 2.1
+ *
+ * @param integer $id
+ *
+ * @return DIWikiPage|null
+ */
+ public function getDataItemById( $id ) {
+ return $this->idEntityFinder->getDataItemById( $id );
+ }
+
+ /**
+ * @since 2.3
+ *
+ * @param integer $id
+ * @param RequestOptions|null $requestOptions
+ *
+ * @return string[]
+ */
+ public function getDataItemsFromList( array $idlist, RequestOptions $requestOptions = null ) {
+ return $this->idEntityFinder->getDataItemsFromList( $idlist, $requestOptions );
+ }
+
+ /**
+ * @deprecated since 3.0, use SMWSql3SmwIds::getDataItemsFromList
+ */
+ public function getDataItemPoolHashListFor( array $idlist, RequestOptions $requestOptions = null ) {
+ return $this->idEntityFinder->getDataItemsFromList( $idlist, $requestOptions );
+ }
+
+ /**
+ * Remove any cache entry for the given data. The key consists of the
+ * parameters $title, $namespace, $interwiki, and $subobject. The
+ * cached data is $id and $sortkey.
+ *
+ * @since 1.8
+ * @param string $title
+ * @param integer $namespace
+ * @param string $interwiki
+ * @param string $subobject
+ */
+ public function deleteCache( $title, $namespace, $interwiki, $subobject ) {
+ $this->idCacheManager->deleteCache( $title, $namespace, $interwiki, $subobject );
+ }
+
+ /**
+ * Move all cached information about subobjects.
+ *
+ * @todo This method is neither efficient nor very convincing
+ * architecturally; it should be redesigned.
+ *
+ * @since 1.8
+ * @param string $oldtitle
+ * @param integer $oldnamespace
+ * @param string $newtitle
+ * @param integer $newnamespace
+ */
+ public function moveSubobjects( $oldtitle, $oldnamespace, $newtitle, $newnamespace ) {
+ // Currently we have no way to change title and namespace across all entries.
+ // Best we can do is clear the cache to avoid wrong hits:
+ if ( $oldnamespace != SMW_NS_PROPERTY || $newnamespace != SMW_NS_PROPERTY ) {
+ $this->idCacheManager->deleteCache( $oldtitle, $oldnamespace, '', '' );
+ $this->idCacheManager->deleteCache( $newtitle, $newnamespace, '', '' );
+ }
+ }
+
+ /**
+ * @since 3.0
+ */
+ public function initCache() {
+
+ // Tests indicate that it is more memory efficient to have two
+ // arrays (IDs and sortkeys) than to have one array that stores both
+ // values in some data structure (other than a single string).
+ $this->idCacheManager = $this->factory->newIdCacheManager(
+ self::POOLCACHE_ID,
+ [
+ 'entity.id' => self::MAX_CACHE_SIZE,
+ 'entity.sort' => self::MAX_CACHE_SIZE,
+ 'entity.lookup' => 2000,
+ 'table.hash' => self::MAX_CACHE_SIZE,
+ ]
+ );
+ }
+
+ /**
+ * Return an array of hashes with table names as keys. These
+ * hashes are used to compare new data with old data for each
+ * property-value table when updating data
+ *
+ * @since 1.8
+ *
+ * @param integer $subjectId ID of the page as stored in the SMW IDs table
+ *
+ * @return array
+ */
+ public function getPropertyTableHashes( $sid ) {
+
+ if ( $sid == 0 ) {
+ return [];
+ }
+
+ $hash = null;
+ $cache = $this->idCacheManager->get( 'table.hash' );
+
+ if ( ( $hash = $cache->fetch( $sid ) ) !== false ) {
+ return $hash;
+ }
+
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ $row = $connection->selectRow(
+ self::TABLE_NAME,
+ [ 'smw_proptable_hash' ],
+ 'smw_id=' . $sid,
+ __METHOD__
+ );
+
+ if ( $row !== false ) {
+ $hash = $row->smw_proptable_hash;
+ }
+
+ if ( $hash !== null && $hash !== false && $connection->isType( 'postgres' ) ) {
+ $hash = pg_unescape_bytea( $hash );
+ }
+
+ $hash = $hash === null || $hash === false ? [] : unserialize( $hash );
+ $cache->save( $sid, $hash );
+
+ return $hash;
+ }
+
+ /**
+ * Update the proptable_hash for a given page.
+ *
+ * @since 1.8
+ * @param integer $sid ID of the page as stored in SMW IDs table
+ * @param string[] of hash values with table names as keys
+ */
+ public function setPropertyTableHashes( $sid, $hash = null ) {
+
+ $connection = $this->store->getConnection( 'mw.db' );
+ $update = [];
+
+ if ( $hash === null ) {
+ $update = [ 'smw_proptable_hash' => $hash, 'smw_rev' => null ];
+ } elseif ( is_array( $hash ) ) {
+ $update = [ 'smw_proptable_hash' => serialize( $hash ) ];
+ } else {
+ throw new RuntimeException( "Expected a null or an array as value!");
+ }
+
+ $connection->update(
+ self::TABLE_NAME,
+ $update,
+ [ 'smw_id' => $sid ],
+ __METHOD__
+ );
+
+ $this->setPropertyTableHashesCache( $sid, $hash );
+
+ if ( $hash === null ) {
+ $this->idCacheManager->deleteCacheById( $sid );
+ }
+ }
+
+ /**
+ * Temporarily cache a property tablehash that has been retrieved for
+ * the given SMW ID.
+ *
+ * @since 1.8
+ * @param $id integer
+ * @param $propertyTableHash string
+ */
+ /**
+ * Temporarily cache a property tablehash that has been retrieved for
+ * the given SMW ID.
+ *
+ * @since 1.8
+ * @param $id integer
+ * @param $propertyTableHash string
+ */
+ protected function setPropertyTableHashesCache( $sid, $hash ) {
+
+ // never cache 0
+ if ( $sid == 0 ) {
+ return;
+ }
+
+ if ( $hash === null ) {
+ $hash = [];
+ } elseif ( is_string( $hash ) ) {
+ $hash = unserialize( $hash );
+ }
+
+ $cache = $this->idCacheManager->get( 'table.hash' );
+ $cache->save( $sid, $hash );
+ }
+
+ /**
+ * Returns store Id table name
+ *
+ * @return string
+ */
+ public function getIdTable() {
+ return self::TABLE_NAME;
+ }
+
+}