summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_SQLStore3_Writers.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_SQLStore3_Writers.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_SQLStore3_Writers.php825
1 files changed, 825 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_SQLStore3_Writers.php b/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_SQLStore3_Writers.php
new file mode 100644
index 00000000..bc8ef57e
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/includes/storage/SQLStore/SMW_SQLStore3_Writers.php
@@ -0,0 +1,825 @@
+<?php
+
+use SMW\ApplicationFactory;
+use SMW\ChangePropListener;
+use SMW\DIProperty;
+use SMW\DIWikiPage;
+use SMW\MediaWiki\Jobs\UpdateJob;
+use SMW\MediaWiki\Deferred\ChangeTitleUpdate;
+use SMW\SemanticData;
+use SMW\Parameters;
+use SMW\SQLStore\PropertyStatisticsTable;
+use SMW\SQLStore\PropertyTableRowDiffer;
+
+/**
+ * Class Handling all the write and update methods for SMWSQLStore3.
+ *
+ * @note Writing may also require some reading operations. Operations that are
+ * only needed in helper methods of this class should be implemented here, not
+ * in SMWSQLStore3Readers.
+ *
+ * @author Markus Krötzsch
+ * @author Jeroen De Dauw
+ * @author Nischay Nahata
+ *
+ * @since 1.8
+ * @ingroup SMWStore
+ */
+class SMWSQLStore3Writers {
+
+ /**
+ * The store used by this store writer.
+ *
+ * @since 1.8
+ * @var SMWSQLStore3
+ */
+ protected $store;
+
+ /**
+ * @var SQLStoreFactory
+ */
+ private $factory;
+
+ /**
+ * @var PropertyTableRowDiffer
+ */
+ private $propertyTableRowDiffer;
+
+ /**
+ * @var PropertyTableUpdater
+ */
+ private $propertyTableUpdater;
+
+ /**
+ * @var SemanticDataLookup
+ */
+ private $semanticDataLookup;
+
+ /**
+ * @var IdChanger
+ */
+ private $idChanger;
+
+ /**
+ * @since 1.8
+ *
+ * @param SMWSQLStore3 $parentStore
+ * @param SQLStoreFactory $factory
+ */
+ public function __construct( SMWSQLStore3 $parentStore, $factory ) {
+ $this->store = $parentStore;
+ $this->factory = $factory;
+ $this->propertyTableRowDiffer = $this->factory->newPropertyTableRowDiffer();
+ $this->propertyTableUpdater = $this->factory->newPropertyTableUpdater();
+ $this->semanticDataLookup = $this->factory->newSemanticDataLookup();
+ $this->idChanger = $this->factory->newIdChanger();
+ }
+
+ /**
+ * @see SMWStore::deleteSubject
+ *
+ * @since 1.8
+ * @param Title $title
+ */
+ public function deleteSubject( Title $title ) {
+
+ // @deprecated since 2.1, use 'SMW::SQLStore::BeforeDeleteSubjectComplete'
+ \Hooks::run( 'SMWSQLStore3::deleteSubjectBefore', [ $this->store, $title ] );
+
+ \Hooks::run( 'SMW::SQLStore::BeforeDeleteSubjectComplete', [ $this->store, $title ] );
+
+ // Fetch all possible matches (including any duplicates created by
+ // incomplete rollback or DB deadlock)
+ $idList = $this->store->getObjectIds()->findAllEntitiesThatMatch(
+ $title->getDBkey(),
+ $title->getNamespace()
+ );
+
+ $extensionList = array_flip( $idList );
+ $subject = DIWikiPage::newFromTitle( $title );
+
+ $emptySemanticData = new SemanticData( $subject );
+ $emptySemanticData->setOption( SemanticData::PROC_DELETE, true );
+
+ $subobjectListFinder = $this->factory->newSubobjectListFinder();
+
+ foreach ( $idList as $id ) {
+ $this->doDelete( $id, $subject, $subobjectListFinder, $extensionList );
+ $this->doDataUpdate( $emptySemanticData );
+
+ if ( $this->store->service( 'PropertyTableIdReferenceFinder' )->hasResidualPropertyTableReference( $id ) === false ) {
+ // Mark subject/subobjects with a special IW, the final removal is being
+ // triggered by the `EntityRebuildDispatcher`
+ $this->store->getObjectIds()->updateInterwikiField(
+ $id,
+ $subject,
+ SMW_SQL3_SMWDELETEIW
+ );
+ } else {
+ // Convert the subject into a simple object instance
+ $this->store->getObjectIds()->setPropertyTableHashes(
+ $id,
+ null
+ );
+ }
+ }
+
+ $extensionList = array_keys( $extensionList );
+
+ $this->store->extensionData['delete.list'] = $extensionList;
+
+ // @deprecated since 2.1, use 'SMW::SQLStore::AfterDeleteSubjectComplete'
+ \Hooks::run( 'SMWSQLStore3::deleteSubjectAfter', [ $this->store, $title ] );
+
+ \Hooks::run( 'SMW::SQLStore::AfterDeleteSubjectComplete', [ $this->store, $title ] );
+ }
+
+ private function doDelete( $id, $subject, $subobjectListFinder, &$extensionList ) {
+
+ $this->semanticDataLookup->invalidateCache( $id );
+
+ if ( $subject->getNamespace() === SMW_NS_CONCEPT ) { // make sure to clear caches
+ $db = $this->store->getConnection();
+
+ $db->delete(
+ SMWSQLStore3::CONCEPT_TABLE,
+ [ 's_id' => $id ],
+ 'SMW::deleteSubject::Conc'
+ );
+
+ $db->delete(
+ SMWSQLStore3::CONCEPT_CACHE_TABLE,
+ [ 'o_id' => $id ],
+ 'SMW::deleteSubject::Conccache'
+ );
+ }
+
+ $subject->setId( $id );
+
+ foreach( $subobjectListFinder->find( $subject ) as $subobject ) {
+ $extensionList[$subobject->getId()] = true;
+
+ $this->store->getObjectIds()->updateInterwikiField(
+ $subobject->getId(),
+ $subobject,
+ SMW_SQL3_SMWDELETEIW
+ );
+ }
+ }
+
+ /**
+ * @see SMWStore::doDataUpdate
+ *
+ * @since 1.8
+ * @param SMWSemanticData $data
+ */
+ public function doDataUpdate( SMWSemanticData $semanticData ) {
+ \Hooks::run( 'SMWSQLStore3::updateDataBefore', [ $this->store, $semanticData ] );
+
+ $subject = $semanticData->getSubject();
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ $subobjectListFinder = $this->factory->newSubobjectListFinder();
+
+ $changeOp = $this->factory->newChangeOp(
+ $subject
+ );
+
+ $this->propertyTableRowDiffer->setChangeOp(
+ $changeOp
+ );
+
+ $changePropListener = $this->factory->newChangePropListener();
+ $hierarchyLookup = $this->factory->newHierarchyLookup();
+
+ // #2698
+ $hierarchyLookup->addListenersTo(
+ $changePropListener
+ );
+
+ $changePropListener->loadListeners(
+ $this->store
+ );
+
+ // Update data about our main subject
+ $this->doFlatDataUpdate( $semanticData );
+ $sid = $subject->getId();
+
+ // Update data about our subobjects
+ $subSemanticData = $semanticData->getSubSemanticData();
+ $connection = $this->store->getConnection( 'mw.db' );
+
+ $connection->beginAtomicTransaction( __METHOD__ );
+
+ foreach( $subSemanticData as $subobjectData ) {
+ $this->doFlatDataUpdate( $subobjectData );
+ }
+
+ $deleteList = [];
+
+ // Mark subobjects without reference to be deleted
+ foreach( $subobjectListFinder->find( $subject ) as $subobject ) {
+ if( !$semanticData->hasSubSemanticData( $subobject->getSubobjectName() ) ) {
+
+ $this->doFlatDataUpdate( new SemanticData( $subobject ) );
+ $deleteList[] = $subobject->getId();
+
+ $this->store->getObjectIds()->updateInterwikiField(
+ $subobject->getId(),
+ $subobject,
+ SMW_SQL3_SMWDELETEIW
+ );
+ }
+ }
+
+ if ( ( $rev_id = $semanticData->getExtensionData( 'revision_id' ) ) !== null ) {
+ $this->store->getObjectIds()->updateRevField( $sid, $rev_id );
+ }
+
+ $connection->endAtomicTransaction( __METHOD__ );
+ $connection->beginAtomicTransaction( __METHOD__ );
+
+ // Store the diff in cache so any post processing has a chance to find
+ // what entities and values were changed
+ $changeDiff = $changeOp->newChangeDiff();
+ $changeDiff->save( ApplicationFactory::getInstance()->getCache() );
+
+ $changePropListener->callListeners();
+
+ $this->store->extensionData['delete.list'] = $deleteList;
+ $this->store->extensionData['change.diff'] = $changeDiff;
+
+ // Deprecated since 2.3, use SMW::SQLStore::AfterDataUpdateComplete
+ \Hooks::run( 'SMWSQLStore3::updateDataAfter', [ $this->store, $semanticData ] );
+
+ \Hooks::run( 'SMW::SQLStore::AfterDataUpdateComplete', [
+ $this->store,
+ $semanticData,
+ $changeOp
+ ] );
+
+ $connection->endAtomicTransaction( __METHOD__ );
+ }
+
+ /**
+ * Update the store to contain the given data, without taking any
+ * subobject data into account.
+ *
+ * @since 1.8
+ * @param SMWSemanticData $data
+ */
+ protected function doFlatDataUpdate( SMWSemanticData $data ) {
+ $subject = $data->getSubject();
+
+ // Take care of redirects
+ $redirects = $data->getPropertyValues( new SMW\DIProperty( '_REDI' ) );
+
+ if ( count( $redirects ) > 0 ) {
+ $redirect = end( $redirects ); // at most one redirect per page
+ $this->updateRedirects(
+ $subject->getDBkey(),
+ $subject->getNamespace(),
+ $redirect->getDBkey(),
+ $redirect->getNameSpace()
+ );
+ // Stop here:
+ // * no support for annotations on redirect pages
+ // * updateRedirects takes care of deleting any previous data
+ return;
+ } else {
+ $this->updateRedirects(
+ $subject->getDBkey(),
+ $subject->getNamespace()
+ );
+ }
+
+ // Find an approriate sortkey, the field is influenced by various
+ // elements incl. DEFAULTSORT and can be altered without modifying
+ // any other annotation.
+ $sortKey = $this->makeSortKey( $subject, $data );
+
+ // Always fetch an ID which is either recalled from cache or is created.
+ // Doing so ensures that the sortkey and namespace data are updated
+ // to both the DB and the cache.
+ $sid = $this->store->getObjectIds()->makeSMWPageID(
+ $subject->getDBkey(),
+ $subject->getNamespace(),
+ $subject->getInterwiki(),
+ $subject->getSubobjectName(),
+ true,
+ $sortKey,
+ true
+ );
+
+ $subject->setSortKey( $sortKey );
+ $subject->setId( $sid );
+
+ // Find any potential duplicate entries for the current subject and
+ // if matched, mark them as to be deleted
+ $idList = $this->store->getObjectIds()->findAllEntitiesThatMatch(
+ $subject->getDBkey(),
+ $subject->getNamespace(),
+ $subject->getInterwiki(),
+ $subject->getSubobjectName()
+ );
+
+ foreach ( $idList as $id ) {
+ if ( $id != $sid ) {
+ $this->store->getObjectIds()->updateInterwikiField(
+ $id,
+ $subject,
+ SMW_SQL3_SMWDELETEIW
+ );
+ }
+ }
+
+ // Take care of all remaining property table data
+ list( $insertRows, $deleteRows, $newHashes ) = $this->propertyTableRowDiffer->computeTableRowDiff(
+ $sid,
+ $data
+ );
+
+ $params = new Parameters(
+ [
+ 'insert_rows' => $insertRows,
+ 'delete_rows' => $deleteRows,
+ 'new_hashes' => $newHashes
+ ]
+ );
+
+ $this->propertyTableUpdater->update( $sid, $params );
+
+ if ( $redirects === [] && $subject->getSubobjectName() === '' ) {
+
+ $dataItemFromId = $this->store->getObjectIds()->getDataItemById( $sid );
+
+ // If for some reason the internal redirect marker is still set but no
+ // redirect annotations are known then do update the interwiki field
+ if ( $dataItemFromId !== null && $dataItemFromId->getInterwiki() === SMW_SQL3_SMWREDIIW ) {
+ $this->store->getObjectIds()->updateInterwikiField( $sid, $subject );
+ }
+ }
+
+ // Update caches (may be important if jobs are directly following this call)
+ $this->semanticDataLookup->setLookupCache( $sid, $data );
+ }
+
+ private function makeSortKey( $subject, $data ) {
+
+ // Don't mind the delete process
+ if ( $data->getOption( SemanticData::PROC_DELETE ) ) {
+ return '';
+ }
+
+ $property = new DIProperty( '_SKEY' );
+
+ // Take care of the sortkey
+ $pv = $data->getPropertyValues( $property );
+ $dataItem = end( $pv );
+
+ if ( $dataItem instanceof SMWDIBlob ) {
+ $sortkey = $dataItem->getString();
+ } elseif ( $data->getExtensionData( 'sort.extension' ) !== null ) {
+ $sortkey = $data->getExtensionData( 'sort.extension' );
+ } else {
+ $sortkey = $subject->getSortKey();
+ }
+
+ // Extend the subobject sortkey in case no @sortkey was given for an
+ // entity
+ if ( $subject->getSubobjectName() !== '' && !$dataItem instanceof SMWDIBlob ) {
+
+ // Add sort data from some dedicated containers (of a record or
+ // reference type etc.) otherwise use the sobj name as extension
+ // to distinguish each entity
+ if ( $data->getExtensionData( 'sort.data' ) !== null ) {
+ $sortkey .= '#' . $data->getExtensionData( 'sort.data' );
+ } else {
+ $sortkey .= '#' . $subject->getSubobjectName();
+ }
+ }
+
+ // #649 Be consistent about how sortkeys are stored therefore always
+ // normalize even for usages like {{DEFAULTSORT: Foo_bar }}
+ $sortkey = str_replace( '_', ' ', $sortkey );
+
+ return $sortkey;
+ }
+
+ /**
+ * Implementation of SMWStore::changeTitle(). In contrast to
+ * updateRedirects(), this function does not simply write a redirect
+ * from the old page to the new one, but also deletes all data that may
+ * already be stored for the new title (normally the new title should
+ * belong to an empty page that has no data but at least it could have a
+ * redirect to the old page), and moves all data that exists for the old
+ * title to the new location. Thus, the function executes three steps:
+ * delete data at newtitle, move data from oldtitle to newtitle, and set
+ * redirect from oldtitle to newtitle. In some cases, the goal can be
+ * achieved more efficiently, e.g. if the new title does not occur in SMW
+ * yet: then we can just change the ID records for the titles instead of
+ * changing all data tables
+ *
+ * Note that the implementation ignores the MediaWiki IDs since this
+ * store has its own ID management. Also, the function requires that both
+ * titles are local, i.e. have empty interwiki prefix.
+ *
+ * @todo Currently the sortkey is not moved with the remaining data. It is
+ * not possible to move it reliably in all cases: we cannot distinguish an
+ * unset sortkey from one that was set to the name of oldtitle. Maybe use
+ * update jobs right away?
+ *
+ * @since 1.8
+ *
+ * @param Title $oldTitle
+ * @param Title $newTitle
+ * @param integer $pageId
+ * @param integer $redirectId
+ */
+ public function changeTitle( Title $oldTitle, Title $newTitle, $pageId, $redirectId = 0 ) {
+ global $smwgQEqualitySupport;
+
+ \Hooks::run(
+ 'SMW::SQLStore::BeforeChangeTitleComplete',
+ [ $this->store, $oldTitle, $newTitle, $pageId, $redirectId ]
+ );
+
+ $db = $this->store->getConnection();
+
+ // get IDs but do not resolve redirects:
+ $sid = $this->store->getObjectIds()->getSMWPageID(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace(),
+ '',
+ '',
+ false
+ );
+
+ $tid = $this->store->getObjectIds()->getSMWPageID(
+ $newTitle->getDBkey(),
+ $newTitle->getNamespace(),
+ '',
+ '',
+ false
+ );
+
+ // Easy case: target not used anywhere yet, just hijack its title for our current id
+ if ( ( $tid == 0 ) && ( $smwgQEqualitySupport != SMW_EQ_NONE ) ) {
+ // This condition may not hold even if $newtitle is
+ // currently unused/non-existing since we keep old IDs.
+ // If equality support is off, then this simple move
+ // does too much; fall back to general case below.
+ if ( $sid != 0 ) { // change id entry to refer to the new title
+ // Note that this also changes the reference for internal objects (subobjects)
+ $db->update(
+ SMWSql3SmwIds::TABLE_NAME,
+ [
+ 'smw_title' => $newTitle->getDBkey(),
+ 'smw_namespace' => $newTitle->getNamespace(),
+ 'smw_iw' => ''
+ ],
+ [
+ 'smw_title' => $oldTitle->getDBkey(),
+ 'smw_namespace' => $oldTitle->getNamespace(),
+ 'smw_iw' => ''
+ ],
+ __METHOD__
+ );
+
+ $this->store->getObjectIds()->moveSubobjects(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace(),
+ $newTitle->getDBkey(),
+ $newTitle->getNamespace()
+ );
+
+ $this->store->getObjectIds()->setCache(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace(),
+ '',
+ '',
+ 0,
+ ''
+ );
+
+ // We do not know the new sortkey, so just clear the cache:
+ $this->store->getObjectIds()->deleteCache(
+ $newTitle->getDBkey(),
+ $newTitle->getNamespace(),
+ '',
+ ''
+ );
+
+ } else { // make new (target) id for use in redirect table
+ $sid = $this->store->getObjectIds()->makeSMWPageID(
+ $newTitle->getDBkey(),
+ $newTitle->getNamespace(),
+ '',
+ ''
+ );
+ } // at this point, $sid is the id of the target page (according to the IDs table)
+
+ // make redirect id for oldtitle:
+ $this->store->getObjectIds()->makeSMWPageID(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace(),
+ SMW_SQL3_SMWREDIIW,
+ ''
+ );
+
+ $this->store->getObjectIds()->addRedirect(
+ $sid,
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace()
+ );
+
+ $propertyStatisticsStore = $this->factory->newPropertyStatisticsStore();
+
+ $propertyStatisticsStore->addToUsageCount(
+ $this->store->getObjectIds()->getSMWPropertyID( new SMW\DIProperty( '_REDI' ) ),
+ 1
+ );
+
+ /// NOTE: there is the (bad) case that the moved page is a redirect. As chains of
+ /// redirects are not supported by MW or SMW, the above is maximally correct in this case too.
+ /// NOTE: this temporarily leaves existing redirects to oldtitle point to newtitle as well, which
+ /// will be lost after the next update. Since double redirects are an error anyway, this is not
+ /// a bad behavior: everything will continue to work until the existing redirects are updated,
+ /// which will hopefully be done to fix the double redirect.
+ } else { // General move method: should always be correct
+ // (equality support respected when updating redirects)
+
+ // Delete any existing data (including redirects) from old title
+ $emptyNewSemanticData = new SMWSemanticData( SMWDIWikiPage::newFromTitle( $oldTitle ) );
+ $this->doDataUpdate( $emptyNewSemanticData );
+
+ // Move all data of old title to new position:
+ if ( $sid != 0 ) {
+ $this->idChanger->change(
+ $sid,
+ $tid,
+ $oldTitle->getNamespace(),
+ $newTitle->getNamespace(),
+ true,
+ false
+ );
+ }
+
+ // Associate internal objects (subobjects) with the new title:
+ $table = $db->tableName( SMWSql3SmwIds::TABLE_NAME );
+
+ $values = [
+ 'smw_title' => $newTitle->getDBkey(),
+ 'smw_namespace' => $newTitle->getNamespace(),
+ 'smw_iw' => ''
+ ];
+
+ $sql = "UPDATE $table SET " . $db->makeList( $values, LIST_SET ) .
+ ' WHERE smw_title = ' . $db->addQuotes( $oldTitle->getDBkey() ) . ' AND ' .
+ 'smw_namespace = ' . $db->addQuotes( $oldTitle->getNamespace() ) . ' AND ' .
+ 'smw_iw = ' . $db->addQuotes( '' ) . ' AND ' .
+ 'smw_subobject != ' . $db->addQuotes( '' ); // The "!=" is why we cannot use MW array syntax here
+
+ $db->query( $sql, __METHOD__ );
+
+ $this->store->getObjectIds()->moveSubobjects(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace(),
+ $newTitle->getDBkey(),
+ $newTitle->getNamespace()
+ );
+
+ // $redirid == 0 means that the oldTitle was not supposed to be a redirect
+ // (oldTitle is delete from the db) but instead of deleting all
+ // references we will still copy data from old to new during updateRedirects()
+ // and clear the semantic data container for the oldTitle instance
+ // to ensure that no ghost references exists for an deleted oldTitle
+ // @see Title::moveTo(), createRedirect
+ if ( $redirectId == 0 ) {
+
+ // Delete any existing data (including redirects) from old title
+ $this->updateRedirects(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace()
+ );
+
+ } else {
+
+ // Write a redirect from old title to new one:
+ // (this also updates references in other tables as needed.)
+ // TODO: may not be optimal for the standard case that newtitle
+ // existed and redirected to oldtitle (PERFORMANCE)
+ $this->updateRedirects(
+ $oldTitle->getDBkey(),
+ $oldTitle->getNamespace(),
+ $newTitle->getDBkey(),
+ $newTitle->getNamespace()
+ );
+
+ }
+
+ }
+
+ if ( $redirectId == 0 ) {
+ $oldTitle = null;
+ }
+
+ ChangeTitleUpdate::addUpdate( $oldTitle, $newTitle );
+ }
+
+ /**
+ * Helper method to write information about some redirect. Various updates
+ * can be necessary if redirects are resolved as identities in SMW. The
+ * title and namespace of the affected page and of its updated redirect
+ * target are given. The target can be empty ('') to delete any redirect.
+ * Returns the canonical ID that is now to be used for the subject.
+ *
+ * This method does not change the ids of the affected pages, and thus it
+ * is not concerned with updates of the data that is currently stored for
+ * the subject. Normally, a subject that is a redirect will not have other
+ * data, but this method does not depend on this.
+ *
+ * @note Please make sure you fully understand this code before making any
+ * changes here. Keeping the redirect structure consistent is important,
+ * and errors in this code can go unnoticed for quite some time.
+ *
+ * @note This method merely handles the addition or deletion of a redirect
+ * statement in the wiki. It does not assume that any page contents has
+ * been changed (e.g. moved). See changeTitle() for additional handling in
+ * this case.
+ *
+ * @todo Clean up this code.
+ *
+ * @since 1.8
+ * @param string $subject_t
+ * @param integer $subject_ns
+ * @param string $curtarget_t
+ * @param integer $curtarget_ns
+ * @return integer the new canonical ID of the subject
+ */
+ protected function updateRedirects( $subject_t, $subject_ns, $curtarget_t = '', $curtarget_ns = -1 ) {
+ global $smwgQEqualitySupport;
+
+ $count = 0; //track count changes for redi property
+ $db = $this->store->getConnection();
+
+ // *** First get id of subject, old redirect target, and current (new) redirect target ***//
+
+ $sid_sort = '';
+
+ // find real id of subject, if any
+ $sid = $this->store->getObjectIds()->getSMWPageIDandSort(
+ $subject_t,
+ $subject_ns,
+ '',
+ '',
+ $sid_sort,
+ false
+ );
+
+ /// NOTE: $sid can be 0 here; this is useful to know since it means that fewer table updates are needed
+ $new_tid = $curtarget_t ? ( $this->store->getObjectIds()->makeSMWPageID( $curtarget_t, $curtarget_ns, '', '', false ) ) : 0; // real id of new target, if given
+
+ $old_tid = $this->store->getObjectIds()->findRedirect(
+ $subject_t,
+ $subject_ns
+ );
+
+ /// NOTE: $old_tid and $new_tid both (intentionally) ignore further redirects: no redirect chains
+
+ if ( $old_tid == $new_tid ) { // no change, all happy
+ return ( $new_tid == 0 ) ? $sid : $new_tid;
+ } // note that this means $old_tid != $new_tid in all cases below
+
+ // *** Make relevant changes in property tables (don't write the new redirect yet) ***//
+ $jobs = [];
+
+ if ( ( $old_tid == 0 ) && ( $sid != 0 ) && ( $smwgQEqualitySupport != SMW_EQ_NONE ) ) { // new redirect
+ // $smwgQEqualitySupport requires us to change all tables' page references from $sid to $new_tid.
+ // Since references must not be 0, we don't have to do this is $sid == 0.
+ $this->idChanger->change(
+ $sid,
+ $new_tid,
+ $subject_ns,
+ $curtarget_ns,
+ false,
+ true
+ );
+
+ } elseif ( $old_tid != 0 ) { // existing redirect is changed or deleted
+
+ $count--;
+
+ $this->store->getObjectIds()->updateRedirect(
+ $old_tid,
+ $subject_t,
+ $subject_ns
+ );
+ }
+
+ // *** Finally, write the new redirect data ***//
+
+ if ( $new_tid != 0 ) { // record a new redirect
+ // Redirecting done right:
+ // (1) make a new ID with iw SMW_SQL3_SMWREDIIW or
+ // change iw field of current ID in this way,
+ // (2) write smw_fpt_redi table,
+ // (3) update canonical cache.
+ // This order must be obeyed unless you really understand what you are doing!
+
+ if ( ( $old_tid == 0 ) && ( $smwgQEqualitySupport != SMW_EQ_NONE ) ) {
+ // mark subject as redirect (if it was no redirect before)
+ if ( $sid == 0 ) { // every redirect page must have an ID
+ $sid = $this->store->getObjectIds()->makeSMWPageID(
+ $subject_t,
+ $subject_ns,
+ SMW_SQL3_SMWREDIIW,
+ '',
+ false
+ );
+ } else {
+ $db->update(
+ SMWSql3SmwIds::TABLE_NAME,
+ [ 'smw_iw' => SMW_SQL3_SMWREDIIW ],
+ [ 'smw_id' => $sid ],
+ __METHOD__
+ );
+
+ $this->store->getObjectIds()->setCache(
+ $subject_t,
+ $subject_ns,
+ '',
+ '',
+ 0,
+ ''
+ );
+
+ $this->store->getObjectIds()->setCache(
+ $subject_t,
+ $subject_ns,
+ SMW_SQL3_SMWREDIIW,
+ '',
+ $sid,
+ $sid_sort
+ );
+ }
+ }
+
+ $this->store->getObjectIds()->addRedirect(
+ $new_tid,
+ $subject_t,
+ $subject_ns
+ );
+
+ $count++;
+
+ } else { // delete old redirect
+ // This case implies $old_tid != 0 (or we would have new_tid == old_tid above).
+ // Therefore $subject had a redirect, and it must also have an ID.
+ // This shows that $sid != 0 here.
+ if ( $smwgQEqualitySupport != SMW_EQ_NONE ) { // mark subject as non-redirect
+
+ $db->update(
+ SMWSql3SmwIds::TABLE_NAME,
+ [ 'smw_iw' => '' ],
+ [ 'smw_id' => $sid ],
+ __METHOD__
+ );
+
+ $this->store->getObjectIds()->setCache(
+ $subject_t,
+ $subject_ns,
+ SMW_SQL3_SMWREDIIW,
+ '',
+ 0,
+ ''
+ );
+
+ $this->store->getObjectIds()->setCache(
+ $subject_t,
+ $subject_ns,
+ '',
+ '',
+ $sid,
+ $sid_sort
+ );
+ }
+ }
+
+ // *** Flush some caches to be safe, though they are not essential in runs with redirect updates ***//
+ $this->semanticDataLookup->invalidateCache( $sid );
+ $this->semanticDataLookup->invalidateCache( $new_tid );
+ $this->semanticDataLookup->invalidateCache( $old_tid );
+
+ // *** Update reference count for _REDI property ***//
+ $propertyStatisticsStore = $this->factory->newPropertyStatisticsStore();
+
+ $propertyStatisticsStore->addToUsageCount(
+ $this->store->getObjectIds()->getSMWPropertyID( new SMW\DIProperty( '_REDI' ) ),
+ $count
+ );
+
+ return ( $new_tid == 0 ) ? $sid : $new_tid;
+ }
+
+}