summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/QueryEngine/Fulltext/TextChangeUpdater.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/SQLStore/QueryEngine/Fulltext/TextChangeUpdater.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/src/SQLStore/QueryEngine/Fulltext/TextChangeUpdater.php312
1 files changed, 312 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/QueryEngine/Fulltext/TextChangeUpdater.php b/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/QueryEngine/Fulltext/TextChangeUpdater.php
new file mode 100644
index 00000000..25e94d07
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/QueryEngine/Fulltext/TextChangeUpdater.php
@@ -0,0 +1,312 @@
+<?php
+
+namespace SMW\SQLStore\QueryEngine\Fulltext;
+
+use Onoi\Cache\Cache;
+use Psr\Log\LoggerAwareTrait;
+use SMW\MediaWiki\Database;
+use SMW\ApplicationFactory;
+use SMW\DIWikiPage;
+use SMW\SQLStore\ChangeOp\ChangeDiff;
+use SMW\SQLStore\ChangeOp\ChangeOp;
+use SMW\SQLStore\ChangeOp\TableChangeOp;
+use SMW\Utils\Timer;
+
+/**
+ * @license GNU GPL v2+
+ * @since 2.5
+ *
+ * @author mwjames
+ */
+class TextChangeUpdater {
+
+ use LoggerAwareTrait;
+
+ /**
+ * @var Database
+ */
+ private $connection;
+
+ /**
+ * @var Cache
+ */
+ private $cache;
+
+ /**
+ * @var SearchTableUpdater
+ */
+ private $searchTableUpdater;
+
+ /**
+ * @var boolean
+ */
+ private $asDeferredUpdate = true;
+
+ /**
+ * @var boolean
+ */
+ private $isCommandLineMode = false;
+
+ /**
+ * @var boolean
+ */
+ private $isPrimary = false;
+
+ /**
+ * @since 2.5
+ *
+ * @param Database $connection
+ * @param Cache $cache
+ * @param SearchTableUpdater $searchTableUpdater
+ * @param TextSanitizer $textSanitizer
+ */
+ public function __construct( Database $connection, Cache $cache, SearchTableUpdater $searchTableUpdater ) {
+ $this->connection = $connection;
+ $this->cache = $cache;
+ $this->searchTableUpdater = $searchTableUpdater;
+ }
+
+ /**
+ * @note See comments in the DefaultSettings.php on the smwgFulltextDeferredUpdate setting
+ *
+ * @since 2.5
+ *
+ * @param boolean $asDeferredUpdate
+ */
+ public function asDeferredUpdate( $asDeferredUpdate ) {
+ $this->asDeferredUpdate = (bool)$asDeferredUpdate;
+ }
+
+ /**
+ * When running from commandLine, push updates directly to avoid overhead when
+ * it is known that within that mode transactions are FIFO (i.e. the likelihood
+ * for race conditions of unfinished updates are diminishable).
+ *
+ * @since 2.5
+ *
+ * @param boolean $isCommandLineMode
+ */
+ public function isCommandLineMode( $isCommandLineMode ) {
+ $this->isCommandLineMode = (bool)$isCommandLineMode;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param boolean $isPrimary
+ */
+ public function isPrimary( $isPrimary ) {
+ $this->isPrimary = $isPrimary;
+ }
+
+ /**
+ * @see SMW::SQLStore::AfterDataUpdateComplete hook
+ *
+ * @since 2.5
+ *
+ * @param ChangeOp $changeOp
+ */
+ public function pushUpdates( ChangeOp $changeOp ) {
+
+ if ( !$this->searchTableUpdater->isEnabled() ) {
+ return;
+ }
+
+ Timer::start( __METHOD__ );
+
+ // Update within the same transaction as started by SMW::SQLStore::AfterDataUpdateComplete
+ if ( !$this->asDeferredUpdate || $this->isCommandLineMode || $this->isPrimary ) {
+ return $this->doUpdateFromChangeDiff( $changeOp->newChangeDiff() );
+ }
+
+ if ( !$this->canPostUpdate( $changeOp ) ) {
+ return;
+ }
+
+ $fulltextSearchTableUpdateJob = ApplicationFactory::getInstance()->newJobFactory()->newFulltextSearchTableUpdateJob(
+ $changeOp->getSubject()->getTitle(),
+ [
+ 'slot:id' => $changeOp->getSubject()->getHash()
+ ]
+ );
+
+ $fulltextSearchTableUpdateJob->lazyPush();
+
+ $this->logger->info(
+ [
+ 'Fulltext',
+ 'TextChangeUpdater',
+ 'Table update (as job) scheduled',
+ 'procTime in sec: {procTime}'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'procTime' => Timer::getElapsedTime( __METHOD__, 5 )
+ ]
+ );
+ }
+
+ /**
+ * @see SearchTableUpdateJob::run
+ *
+ * @since 2.5
+ *
+ * @param array|boolan $parameters
+ */
+ public function pushUpdatesFromJobParameters( $parameters ) {
+
+ if ( !$this->searchTableUpdater->isEnabled() || !isset( $parameters['slot:id'] ) || $parameters['slot:id'] === false ) {
+ return;
+ }
+
+ $subject = DIWikiPage::doUnserialize( $parameters['slot:id'] );
+ $changeDiff = ChangeDiff::fetch( $this->cache, $subject );
+
+ if ( $changeDiff !== false ) {
+ return $this->doUpdateFromChangeDiff( $changeDiff );
+ }
+
+ $this->logger->info(
+ [
+ 'Fulltext',
+ 'TextChangeUpdater',
+ 'Failed update (ChangeDiff) on {id}'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'id' => $parameters['slot:id']
+ ]
+ );
+ }
+
+ /**
+ * @since 2.5
+ *
+ * @param ChangeOp $changeOp
+ */
+ public function doUpdateFromChangeDiff( ChangeDiff $changeDiff ) {
+
+ if ( !$this->searchTableUpdater->isEnabled() ) {
+ return;
+ }
+
+ Timer::start( __METHOD__ );
+
+ $textItems = $changeDiff->getTextItems();
+ $diffChangeOps = $changeDiff->getTableChangeOps();
+
+ $changeList = $changeDiff->getChangeListByType( 'insert' );
+ $updates = [];
+
+ // Ensure that any delete operation is being accounted for to avoid that
+ // removed value annotation remain
+ if ( $diffChangeOps !== [] ) {
+ $this->doDeleteFromTableChangeOps( $diffChangeOps );
+ }
+
+ // Build a composite of replacements where a change occurred, this my
+ // contain some false positives
+ foreach ( $textItems as $sid => $textItem ) {
+
+ if ( !isset( $changeList[$sid] ) ) {
+ continue;
+ }
+
+ $this->collectUpdates( $sid, $textItem, $changeList, $updates );
+ }
+
+ foreach ( $updates as $key => $value ) {
+ list( $sid, $pid ) = explode( ':', $key, 2 );
+
+ if ( $this->searchTableUpdater->exists( $sid, $pid ) === false ) {
+ $this->searchTableUpdater->insert( $sid, $pid );
+ }
+
+ $this->searchTableUpdater->update(
+ $sid,
+ $pid,
+ $value
+ );
+ }
+
+ $this->logger->info(
+ [
+ 'Fulltext',
+ 'TextChangeUpdater',
+ 'Table update completed',
+ 'procTime in sec: {procTime}'
+ ],
+ [
+ 'method' => __METHOD__,
+ 'role' => 'developer',
+ 'procTime' => Timer::getElapsedTime( __METHOD__, 5 )
+ ]
+ );
+ }
+
+ private function collectUpdates( $sid, array $textItem, $changeList, &$updates ) {
+
+ $searchTable = $this->searchTableUpdater->getSearchTable();
+
+ foreach ( $textItem as $pid => $text ) {
+
+ // Exempted property -> out
+ if ( $searchTable->isExemptedPropertyById( $pid ) ) {
+ continue;
+ }
+
+ $text = implode( ' ', $text );
+ $key = $sid . ':' . $pid;
+
+ $updates[$key] = !isset( $updates[$key] ) ? $text : $updates[$key] . ' ' . $text;
+ }
+ }
+
+ private function doDeleteFromTableChangeOps( array $tableChangeOps ) {
+ foreach ( $tableChangeOps as $tableChangeOp ) {
+ $this->doDeleteFromTableChangeOp( $tableChangeOp );
+ }
+ }
+
+ private function doDeleteFromTableChangeOp( TableChangeOp $tableChangeOp ) {
+
+ foreach ( $tableChangeOp->getFieldChangeOps( 'delete' ) as $fieldChangeOp ) {
+
+ // Replace s_id for subobjects etc. with the o_id
+ if ( $tableChangeOp->isFixedPropertyOp() ) {
+ $fieldChangeOp->set( 's_id', $fieldChangeOp->has( 'o_id' ) ? $fieldChangeOp->get( 'o_id' ) : $fieldChangeOp->get( 's_id' ) );
+ $fieldChangeOp->set( 'p_id', $tableChangeOp->getFixedPropertyValueBy( 'p_id' ) );
+ }
+
+ if ( !$fieldChangeOp->has( 'p_id' ) ) {
+ continue;
+ }
+
+ $this->searchTableUpdater->delete(
+ $fieldChangeOp->get( 's_id' ),
+ $fieldChangeOp->get( 'p_id' )
+ );
+ }
+ }
+
+ private function canPostUpdate( $changeOp ) {
+
+ $searchTable = $this->searchTableUpdater->getSearchTable();
+ $canPostUpdate = false;
+
+ // Find out whether we should actual initiate an update
+ foreach ( $changeOp->getChangedEntityIdSummaryList() as $id ) {
+ if ( ( $dataItem = $searchTable->getDataItemById( $id ) ) instanceof DIWikiPage && $dataItem->getNamespace() === SMW_NS_PROPERTY ) {
+ if ( !$searchTable->isExemptedPropertyById( $id ) ) {
+ $canPostUpdate = true;
+ break;
+ }
+ }
+ }
+
+ return $canPostUpdate;
+ }
+
+}