jenkins-bot has submitted this change and it was merged.

Change subject: Introducing changes subscription infrastructure.
......................................................................


Introducing changes subscription infrastructure.

This introduces the wb_changes_subscriptions table for
tracking which clients (subscribers) are interested in
changes to which entities.

DEPLOY: The 'useLegacyChangesSubscription' setting should be set to true 
on the repo AND all client wikis, until the wb_changes_subscription table
 on the repo exists and is populated.

Bug: T86184
Change-Id: I120ed3bc4a68f5b6791b176e1e714deba8861b24
---
M client/config/WikibaseClient.default.php
M docs/options.wiki
M repo/Wikibase.php
M repo/config/Wikibase.default.php
A repo/includes/store/sql/ChangesSubscriptionSchemaUpdater.php
A repo/includes/store/sql/ChangesSubscriptionTableBuilder.php
A repo/maintenance/populateChangesSubscription.php
A repo/sql/changes_subscription.sql
A repo/tests/phpunit/includes/store/sql/ChangesSubscriptionTableBuilderTest.php
9 files changed, 635 insertions(+), 1 deletion(-)

Approvals:
  JanZerebecki: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/client/config/WikibaseClient.default.php 
b/client/config/WikibaseClient.default.php
index 501160a..756693f 100644
--- a/client/config/WikibaseClient.default.php
+++ b/client/config/WikibaseClient.default.php
@@ -48,6 +48,9 @@
                // Enable in case wbc_entity_usage does not exist or is not yet 
populated.
                'useLegacyUsageIndex' => false,
 
+               // Enable in case wb_changes_subscription does not exist (on 
the repo) or is not yet populated.
+               'useLegacyChangesSubscription' => false,
+
                /**
                 * @todo this is a bit wikimedia-specific and need to find a 
better place for this stuff,
                 * such as mediawiki-config, mediawiki messages for custom 
orders, or somewhere.
diff --git a/docs/options.wiki b/docs/options.wiki
index e8c3a6d..593d091 100644
--- a/docs/options.wiki
+++ b/docs/options.wiki
@@ -62,6 +62,7 @@
 ;useRedirectTargetColumn: Whether to use the epp_redirect_target column in the 
wb_entity_per_page table for detecting redirects. True per default, can be set 
to false in an environment where the necessary database update can't be 
deployed right away. To set up the table manually, run 
repo/sql/AddEppRedirectTarget.sql to create it, then use 
repo/maintenance/rebuildEntityPerPage.php to rebuild the table if neccessary.
 ;internalEntitySerializerClass: The class name of a serializer that is to be 
used when serializing entities for storage. Defaults to null, causing the 
default entity serializer from the SerializerFactory to be used. Should be set 
to Wikibase\Lib\Serializers\LegacyInternalEntitySerializer for compatibility 
with client wikis that run older code.
 ;transformLegacyFormatOnExport: Whether entity revisions stored in a legacy 
format should be converted on the fly while exporting. Enabled per default, if 
internalEntitySerializerClass is not set, and disable by default if 
internalEntitySerializerClass is set.  Must be disabled if 
internalEntitySerializerClass is set to the legacy serializer.
+;useLegacyChangesSubscription: whether the sitelinks table should be used to 
emulate notification subscriptions. Should be set to true if the approproate 
database schema change has not been deployed yet.
 
 == Client Settings ==
 
@@ -110,4 +111,5 @@
 :;<code>'CachingSqlStore'</code>: No direct access to the repo's database. Use 
cache information received from change notifications. '''Warning:''' dies is 
currently dysfunctional.
 :;<code>null</code>: Automatic, use <code>'DirectSqlStore'</code> if 
<code>repoDatabase</code> is set, <code>'CachingSqlStore'</code> if not.
 : '''Caveat:''' should always be <code>null</code> at the moment, because some 
code relies on the <code>repoDatabase</code> setting to determine whether the 
local cache tables should be used.
-;useLegacyUsageIndex: whether the sitelinks table should be used to emulate 
usage tracking. May be set to true if the approproate database schema change 
has not been deployed yet.
+;useLegacyUsageIndex: whether the sitelinks table should be used to emulate 
usage tracking. Should be set to true if the approproate database schema change 
has not been deployed yet.
+;useLegacyChangesSubscription: whether the sitelinks table should be used to 
emulate notification subscriptions. Should be set to true if the approproate 
database schema change has not been deployed to the repository database yet.
diff --git a/repo/Wikibase.php b/repo/Wikibase.php
index 9be6173..1b1fd4c 100644
--- a/repo/Wikibase.php
+++ b/repo/Wikibase.php
@@ -201,6 +201,9 @@
        $wgHooks['BaseTemplateToolbox'][]               = 
'Wikibase\RepoHooks::onBaseTemplateToolbox';
        $wgHooks['SkinTemplateBuildNavUrlsNav_urlsAfterPermalink'][] = 
'Wikibase\RepoHooks::onSkinTemplateBuildNavUrlsNav_urlsAfterPermalink';
 
+       // update hooks
+       $wgHooks['LoadExtensionSchemaUpdates'][] = 
'\Wikibase\Repo\Store\Sql\ChangesSubscriptionSchemaUpdater::onSchemaUpdate';
+
        // Resource Loader Modules:
        $wgResourceModules = array_merge( $wgResourceModules, include( __DIR__ 
. "/resources/Resources.php" ) );
 
diff --git a/repo/config/Wikibase.default.php b/repo/config/Wikibase.default.php
index a83d0b2..a2be72c 100644
--- a/repo/config/Wikibase.default.php
+++ b/repo/config/Wikibase.default.php
@@ -102,6 +102,8 @@
                        return $uri;
                },
 
+               // Enable in case wb_changes_subscription does not exist or is 
not yet populated.
+               'useLegacyChangesSubscription' => false,
        );
 
        return $defaults;
diff --git a/repo/includes/store/sql/ChangesSubscriptionSchemaUpdater.php 
b/repo/includes/store/sql/ChangesSubscriptionSchemaUpdater.php
new file mode 100644
index 0000000..cf4aabb
--- /dev/null
+++ b/repo/includes/store/sql/ChangesSubscriptionSchemaUpdater.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Wikibase\Repo\Store\Sql;
+
+use DatabaseUpdater;
+use Wikibase\Lib\Reporting\ObservableMessageReporter;
+use Wikibase\Repo\WikibaseRepo;
+
+/**
+ * Schema updater for the wb_changes_subscription table.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class ChangesSubscriptionSchemaUpdater {
+
+       /**
+        * @var DatabaseUpdater
+        */
+       private $dbUpdater;
+
+       /**
+        * @param DatabaseUpdater $dbUpdater
+        */
+       public function __construct( DatabaseUpdater $dbUpdater ) {
+               $this->dbUpdater = $dbUpdater;
+       }
+
+       /**
+        * Static entry point for MediaWiki's LoadExtensionSchemaUpdates hook.
+        *
+        * @param DatabaseUpdater $dbUpdater
+        *
+        * @return bool
+        */
+       public static function onSchemaUpdate( DatabaseUpdater $dbUpdater ) {
+               if ( 
WikibaseRepo::getDefaultInstance()->getSettings()->getSetting( 
'useLegacyChangesSubscription' ) ) {
+                       return true;
+               }
+
+               $changesSubscriptionSchemaUpdater = new self( $dbUpdater );
+               $changesSubscriptionSchemaUpdater->doSchemaUpdate();
+
+               return true;
+       }
+
+       /**
+        * Applies any schema updates
+        */
+       public function doSchemaUpdate() {
+               $table = 'wb_changes_subscription';
+
+               if ( !$this->dbUpdater->tableExists( $table ) ) {
+                       $db = $this->dbUpdater->getDB();
+                       $script = $this->getUpdateScriptPath( 
'changes_subscription', $db->getType() );
+                       $this->dbUpdater->addExtensionTable( $table, $script );
+
+                       // Register function for populating the table.
+                       // Note that this must be done with a static function,
+                       // for reasons that do not need explaining at this 
juncture.
+                       $this->dbUpdater->addExtensionUpdate( array(
+                               array( __CLASS__, 'fillSubscriptionTable' ),
+                               $table
+                       ) );
+               }
+       }
+
+       /**
+        * Static wrapper for EntityUsageTableBuilder::fillUsageTable
+        *
+        * @param DatabaseUpdater $dbUpdater
+        * @param string $table
+        */
+       public static function fillSubscriptionTable( DatabaseUpdater 
$dbUpdater, $table ) {
+               $primer = new ChangesSubscriptionTableBuilder(
+                       wfGetLB(), // would be nice to pass in 
$dbUpdater->getDB().
+                       $table,
+                       1000
+               );
+
+               $reporter = new ObservableMessageReporter();
+               $reporter->registerReporterCallback( function( $msg ) use ( 
$dbUpdater ) {
+                       $dbUpdater->output( "\t$msg\n" );
+               } );
+               $primer->setProgressReporter( $reporter );
+
+               $primer->fillSubscriptionTable();
+       }
+
+       private function getUpdateScriptPath( $name, $type ) {
+               $extensions = array(
+                       '.sql',
+                       '.' . $type . '.sql',
+               );
+
+               foreach ( $extensions as $ext ) {
+                       $path = __DIR__ . '/../../../sql/' . $name . $ext;
+
+                       if ( file_exists( $path ) ) {
+                               return $path;
+                       }
+               }
+
+               throw new \MWException( "Could not find schema update script 
'$name'." );
+       }
+
+}
diff --git a/repo/includes/store/sql/ChangesSubscriptionTableBuilder.php 
b/repo/includes/store/sql/ChangesSubscriptionTableBuilder.php
new file mode 100644
index 0000000..947f7d8
--- /dev/null
+++ b/repo/includes/store/sql/ChangesSubscriptionTableBuilder.php
@@ -0,0 +1,249 @@
+<?php
+
+namespace Wikibase\Repo\Store\Sql;
+
+use DatabaseBase;
+use InvalidArgumentException;
+use LoadBalancer;
+use ResultWrapper;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\Lib\Reporting\ExceptionHandler;
+use Wikibase\Lib\Reporting\LogWarningExceptionHandler;
+use Wikibase\Lib\Reporting\MessageReporter;
+use Wikibase\Lib\Reporting\NullMessageReporter;
+
+/**
+ * Implements initial population (priming) for the wb_changes_subscription 
table,
+ * based on the wb_items_per_site. Any wiki linked via the wb_items_per_site 
table
+ * will be considered a subscriber.
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class ChangesSubscriptionTableBuilder {
+
+       /**
+        * @var LoadBalancer
+        */
+       private $loadBalancer;
+
+       /**
+        * @var int
+        */
+       private $batchSize;
+
+       /**
+        * @var ExceptionHandler
+        */
+       private $exceptionHandler;
+
+       /**
+        * @var MessageReporter
+        */
+       private $progressReporter;
+
+       /**
+        * @param LoadBalancer $loadBalancer
+        * @param string $tableName
+        * @param int $batchSize
+        *
+        * @throws InvalidArgumentException
+        */
+       public function __construct( LoadBalancer $loadBalancer, $tableName, 
$batchSize = 1000 ) {
+               if ( !is_string( $tableName ) ) {
+                       throw new InvalidArgumentException( '$tableName must be 
a string' );
+               }
+
+               if ( !is_int( $batchSize ) || $batchSize < 1 ) {
+                       throw new InvalidArgumentException( '$batchSize must be 
an integer >= 1' );
+               }
+
+               $this->loadBalancer = $loadBalancer;
+               $this->tableName = $tableName;
+               $this->batchSize = $batchSize;
+
+               $this->exceptionHandler = new LogWarningExceptionHandler();
+               $this->progressReporter = new NullMessageReporter();
+       }
+
+       /**
+        * @param MessageReporter $progressReporter
+        */
+       public function setProgressReporter( MessageReporter $progressReporter 
) {
+               $this->progressReporter = $progressReporter;
+       }
+
+       /**
+        * @return MessageReporter
+        */
+       public function getProgressReporter() {
+               return $this->progressReporter;
+       }
+
+       /**
+        * @param ExceptionHandler $exceptionHandler
+        */
+       public function setExceptionHandler( ExceptionHandler $exceptionHandler 
) {
+               $this->exceptionHandler = $exceptionHandler;
+       }
+
+       /**
+        * @return ExceptionHandler
+        */
+       public function getExceptionHandler() {
+               return $this->exceptionHandler;
+       }
+
+       /**
+        * Fill the subscription table with rows based on entries in page_props.
+        *
+        * @param ItemId $startItem The item to start with.
+        */
+       public function fillSubscriptionTable( ItemId $startItem = null ) {
+               $continuation = $startItem === null ? null : array( 
$startItem->getNumericId(), '' );
+
+               while ( true ) {
+                       $count = $this->processSubscriptionBatch( $continuation 
);
+
+                       if ( $count > 0 ) {
+                               $this->progressReporter->reportMessage( 
"Populating subscription table: inserted $count subscriptions, continuing at 
item #{$continuation[0]}." );
+                       } else {
+                               break;
+                       }
+               };
+       }
+
+       /**
+        * @param array &$continuation
+        *
+        * @return int The number of subscriptions inserted.
+        */
+       private function processSubscriptionBatch( &$continuation = array() ) {
+               $db = $this->loadBalancer->getConnection( DB_MASTER );
+
+               $entityPerPage = $this->getSubscriptionBatch( $db, 
$continuation );
+
+               if ( empty( $entityPerPage ) ) {
+                       return 0;
+               }
+
+               $count = $this->insertSubscriptionBatch( $db, $entityPerPage );
+
+               $this->loadBalancer->reuseConnection( $db );
+
+               return $count;
+       }
+
+       /**
+        * @param DatabaseBase $db
+        * @param array[] $subscriptionsPerItem
+        *
+        * @return int The number of rows inserted.
+        */
+       private function insertSubscriptionBatch( DatabaseBase $db, array 
$subscriptionsPerItem ) {
+               $db->startAtomic( __METHOD__ );
+
+               $c = 0;
+               foreach ( $subscriptionsPerItem as $itemId => $subscribers ) {
+                       $rows = $this->makeSubscriptionRows( $itemId, 
$subscribers );
+
+                       $db->insert(
+                               $this->tableName,
+                               $rows,
+                               __METHOD__,
+                               array(
+                                       'IGNORE'
+                               )
+                       );
+
+                       $c+= count( $rows );
+               }
+
+               $db->endAtomic( __METHOD__ );
+               return $c;
+       }
+
+       /**
+        * @param DatabaseBase $db
+        * @param array &$continuation
+        *
+        * @return array[] An associative array mapping item IDs to lists of 
site IDs.
+        */
+       private function getSubscriptionBatch( DatabaseBase $db, &$continuation 
= array() ) {
+
+               if ( empty( $continuation ) ) {
+                       $continuationCondition = '1';
+               } else {
+                       list( $fromItemId, $fromSiteId ) = $continuation;
+                       $continuationCondition = 'ips_item_id > ' . 
(int)$fromItemId
+                               . ' OR ( '
+                                       . 'ips_item_id = ' . (int)$fromItemId
+                                       . ' AND '
+                                       . 'ips_site_id > ' . $db->addQuotes( 
$fromSiteId )
+                               . ' )';
+               }
+
+               $res = $db->select(
+                       'wb_items_per_site',
+                       array( 'ips_item_id', 'ips_site_id' ),
+                       $continuationCondition,
+                       __METHOD__,
+                       array(
+                               'LIMIT' => $this->batchSize,
+                               'ORDER BY ips_item_id, ips_site_id'
+                       )
+               );
+
+               return $this->slurpSubscriptions( $res, $continuation );
+       }
+
+       /**
+        * @param ResultWrapper $res A result set with the ips_item_id and 
ips_site_id fields
+        *        set for each row.
+        * @param array &$continuation
+        *
+        * @return array[] An associative array mapping item IDs to lists of 
site IDs.
+        */
+       private function slurpSubscriptions( ResultWrapper $res, &$continuation 
= array() ) {
+               $entityPerPage = array();
+
+               $currentId = 0;
+               $key = null;
+
+               foreach ( $res as $row ) {
+                       if ( $row->ips_item_id != $currentId ) {
+                               $currentId = $row->ips_item_id;
+                               $key = ItemId::newFromNumber( $currentId 
)->getSerialization();
+                       }
+
+                       $entityPerPage[$key][] = $row->ips_site_id;
+                       $continuation = array( $currentId, $row->ips_site_id );
+               }
+
+               return $entityPerPage;
+       }
+
+       /**
+        * Returns a list of rows for insertion, using DatabaseBase's multi-row 
insert mechanism.
+        * Each row is represented as array( $itemId, $subscriber ).
+        *
+        * @param string $itemId
+        * @param string[] $subscribers
+        *
+        * @return array[] rows
+        */
+       private function makeSubscriptionRows( $itemId, array $subscribers ) {
+               $rows = array();
+
+               foreach ( $subscribers as $subscriber ) {
+                       $rows[] = array(
+                               'cs_entity_id' => $itemId,
+                               'cs_subscriber_id' => $subscriber
+                       );
+               }
+
+               return $rows;
+       }
+
+}
diff --git a/repo/maintenance/populateChangesSubscription.php 
b/repo/maintenance/populateChangesSubscription.php
new file mode 100644
index 0000000..fc8818a
--- /dev/null
+++ b/repo/maintenance/populateChangesSubscription.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Wikibase;
+
+use LoggedUpdateMaintenance;
+use Wikibase\DataModel\Entity\EntityIdParsingException;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\Repo\Store\Sql\ChangesSubscriptionTableBuilder;
+use Wikibase\Repo\WikibaseRepo;
+use Wikibase\Lib\Reporting\ObservableMessageReporter;
+use Wikibase\Lib\Reporting\ReportingExceptionHandler;
+
+$basePath = getenv( 'MW_INSTALL_PATH' ) !== false ? getenv( 'MW_INSTALL_PATH' 
) : __DIR__ . '/../../../..';
+
+require_once $basePath . '/maintenance/Maintenance.php';
+
+/**
+ * Maintenance script for populating wb_changes_subscription based on the 
page_props table.
+ *
+ * @since 0.4
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class PopulateChangesSubscription extends LoggedUpdateMaintenance {
+
+       public function __construct() {
+               $this->mDescription = 'Populate the wb_changes_subscription 
table based on entries in page_props.';
+
+               $this->addOption( 'start-item', "The page ID to start from.", 
false, true );
+
+               parent::__construct();
+
+               $this->setBatchSize( 1000 );
+       }
+
+       /**
+        * @see LoggedUpdateMaintenance::doDBUpdates
+        *
+        * @return boolean
+        */
+       public function doDBUpdates() {
+               if ( !defined( 'WB_VERSION' ) ) {
+                       $this->output( "You need to have Wikibase enabled in 
order to use this maintenance script!\n\n" );
+                       exit;
+               }
+
+               $idParser = 
WikibaseRepo::getDefaultInstance()->getEntityIdParser();
+               $startItemOption = $this->getOption( 'start-item' );
+
+               $startItem = $startItemOption === null ? null : 
$idParser->parse( $startItemOption );
+
+               if ( $startItem !== null && !( $startItem instanceof ItemId ) ) 
{
+                       throw new EntityIdParsingException( 'Not an Item ID: ' 
. $startItemOption );
+               }
+
+               $reporter = new ObservableMessageReporter();
+               $reporter->registerReporterCallback(
+                       array( $this, 'report' )
+               );
+
+               $builder = new ChangesSubscriptionTableBuilder(
+                       wfGetLB(),
+                       'wb_changes_subscription',
+                       $this->mBatchSize
+               );
+
+               $builder->setProgressReporter( $reporter );
+               $builder->setExceptionHandler( new ReportingExceptionHandler( 
$reporter ) );
+
+               $builder->fillSubscriptionTable( $startItem );
+               return true;
+       }
+
+       /**
+        * @see LoggedUpdateMaintenance::getUpdateKey
+        *
+        * @return string
+        */
+       public function getUpdateKey() {
+               return 'Wikibase\PopulateChangesSubscription';
+       }
+
+       /**
+        * Outputs a message vis the output() method.
+        *
+        * @since 0.4
+        *
+        * @param $msg
+        */
+       public function report( $msg ) {
+               $this->output( "$msg\n" );
+       }
+
+}
+
+$maintClass = 'Wikibase\PopulateChangesSubscription';
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/repo/sql/changes_subscription.sql 
b/repo/sql/changes_subscription.sql
new file mode 100644
index 0000000..1795a69
--- /dev/null
+++ b/repo/sql/changes_subscription.sql
@@ -0,0 +1,11 @@
+CREATE TABLE IF NOT EXISTS /*_*/wb_changes_subscription (
+  cs_row_id         BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  cs_entity_id      VARBINARY(255) NOT NULL, -- the ID of the entity 
subscribed to
+  cs_subscriber_id  VARBINARY(255) NOT NULL  -- the ID of the subscriber (e.g. 
a domain name or database name)
+) /*$wgDBTableOptions*/;
+
+-- look up a subscription, or all subscribers of an entity
+CREATE UNIQUE INDEX /*i*/cs_entity_id ON /*_*/wb_changes_subscription ( 
cs_entity_id, cs_subscriber_id );
+
+-- look up all subscriptions of a subscriber
+CREATE INDEX /*i*/cs_subscriber_id ON /*_*/wb_changes_subscription ( 
cs_subscriber_id ) ;
diff --git 
a/repo/tests/phpunit/includes/store/sql/ChangesSubscriptionTableBuilderTest.php 
b/repo/tests/phpunit/includes/store/sql/ChangesSubscriptionTableBuilderTest.php
new file mode 100644
index 0000000..e4e7990
--- /dev/null
+++ 
b/repo/tests/phpunit/includes/store/sql/ChangesSubscriptionTableBuilderTest.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace Wikibase\Repo\Tests\Store\Sql;
+
+use PHPUnit_Framework_MockObject_Matcher_Invocation;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\Lib\Reporting\ExceptionHandler;
+use Wikibase\Lib\Reporting\MessageReporter;
+use Wikibase\Repo\Store\Sql\ChangesSubscriptionTableBuilder;
+use Wikibase\Repo\WikibaseRepo;
+
+/**
+ * @covers Wikibase\Repo\Store\Sql\ChangesSubscriptionTableBuilder
+ *
+ * @group Wikibase
+ * @group WikibaseRepo
+ * @group WikibaseUsageTracking
+ * @group Database
+ *
+ * @license GPL 2+
+ * @author Daniel Kinzler
+ */
+class ChangesSubscriptionTableBuilderTest extends \MediaWikiTestCase {
+
+       const TABLE_NAME = 'wb_changes_subscription';
+
+       public function setUp() {
+               if ( 
WikibaseRepo::getDefaultInstance()->getSettings()->getSetting( 
'useLegacyChangesSubscription' ) ) {
+                       $this->markTestSkipped( 'Skipping test for 
ChangesSubscriptionTableBuilder, because the useLegacyChangesSubscription 
option is set.' );
+               }
+
+               $this->tablesUsed[] = self::TABLE_NAME;
+               $this->tablesUsed[] = 'wb_items_per_site';
+
+               parent::setUp();
+       }
+
+       private function getChangesSubscriptionTableBuilder( $batchSize = 10 ) {
+               $loadBalancer = wfGetLB();
+
+               return new ChangesSubscriptionTableBuilder( $loadBalancer, 
self::TABLE_NAME, $batchSize );
+       }
+
+       public function testFillSubscriptionTable() {
+               $this->truncateItemPerSite();
+               $this->putItemPerSite( array(
+                       array( 11, 'dewiki' ),
+                       array( 11, 'enwiki' ),
+                       array( 22, 'dewiki' ),
+                       array( 22, 'frwiki' ),
+               ) );
+
+               $primer = $this->getChangesSubscriptionTableBuilder( 3 );
+               $primer->setProgressReporter( $this->getMessageReporter( 
$this->exactly( 2 ) ) );
+               $primer->setExceptionHandler( $this->getExceptionHandler( 
$this->exactly( 0 ) ) );
+
+               $primer->fillSubscriptionTable();
+
+               $actual = $this->fetchAllSubscriptions();
+               sort( $actual );
+
+               $expected = array(
+                       'dewiki@Q11',
+                       'dewiki@Q22',
+                       'enwiki@Q11',
+                       'frwiki@Q22',
+               );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function testFillSubscriptionTable_startItem() {
+               $this->truncateItemPerSite();
+               $this->putItemPerSite( array(
+                       array( 11, 'dewiki' ),
+                       array( 11, 'enwiki' ),
+                       array( 22, 'dewiki' ),
+                       array( 22, 'frwiki' ),
+               ) );
+
+               $primer = $this->getChangesSubscriptionTableBuilder( 3 );
+               $primer->setProgressReporter( $this->getMessageReporter( 
$this->exactly( 1 ) ) );
+               $primer->setExceptionHandler( $this->getExceptionHandler( 
$this->exactly( 0 ) ) );
+
+               $primer->fillSubscriptionTable( new ItemId( 'Q20' ) );
+
+               $actual = $this->fetchAllSubscriptions();
+               sort( $actual );
+
+               $expected = array(
+                       'dewiki@Q22',
+                       'frwiki@Q22',
+               );
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       private function truncateItemPerSite() {
+               $db = wfGetDB( DB_MASTER );
+               $db->delete( 'wb_items_per_site', '*' );
+       }
+
+       private function putItemPerSite( array $entries ) {
+               $db = wfGetDB( DB_MASTER );
+
+               $db->startAtomic( __METHOD__ );
+
+               foreach ( $entries as $entry ) {
+                       list( $itemId, $siteId ) = $entry;
+                       $db->insert( 'wb_items_per_site', array(
+                               'ips_item_id' => (int)$itemId,
+                               'ips_site_id' => $siteId,
+                               'ips_site_page' => 'Page_about_Q' . $itemId. 
'_on_' . $siteId,
+                       ), __METHOD__ );
+               }
+
+               $db->endAtomic( __METHOD__ );
+       }
+
+       private function fetchAllSubscriptions() {
+               $db = wfGetDB( DB_MASTER );
+
+               $res = $db->select( self::TABLE_NAME, "*", '', __METHOD__ );
+
+               $subscriptions = array();
+               foreach ( $res as $row ) {
+                       $subscriptions[] = $row->cs_subscriber_id . '@' . 
$row->cs_entity_id;
+               }
+
+               return $subscriptions;
+       }
+
+       /**
+        * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher
+        *
+        * @return ExceptionHandler
+        */
+       private function getExceptionHandler( 
PHPUnit_Framework_MockObject_Matcher_Invocation $matcher ) {
+               $mock = $this->getMock( 
'Wikibase\Lib\Reporting\ExceptionHandler' );
+               $mock->expects( $matcher )
+                       ->method( 'handleException' );
+
+               return $mock;
+       }
+
+       /**
+        * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher
+        *
+        * @return MessageReporter
+        */
+       private function getMessageReporter( 
PHPUnit_Framework_MockObject_Matcher_Invocation $matcher ) {
+               $mock = $this->getMock( 
'Wikibase\Lib\Reporting\MessageReporter' );
+               $mock->expects( $matcher )
+                       ->method( 'reportMessage' );
+
+               return $mock;
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/183839
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I120ed3bc4a68f5b6791b176e1e714deba8861b24
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: Adrian Lang <adrian.he...@wikimedia.de>
Gerrit-Reviewer: Aude <aude.w...@gmail.com>
Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: JanZerebecki <jan.wikime...@zerebecki.de>
Gerrit-Reviewer: Jeroen De Dauw <jeroended...@gmail.com>
Gerrit-Reviewer: Springle <sprin...@wikimedia.org>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.maet...@wikimedia.de>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to