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

Change subject: [SCHEMA CHANGE] Add corresponding *_user_wiki field to all 
*_user_id fields
......................................................................


[SCHEMA CHANGE] Add corresponding *_user_wiki field to all *_user_id fields

This is to distinguish users and ips from different wikis especially
for pages like contributions.

The steps involved:

1. deploy https://gerrit.wikimedia.org/r/#/c/112794/
2. add the columns to production wikis
3. deploy the code in this patch so the new columns start collecting data
4. run maintenance script to populate existing records
5. update cache version key
6. patch to use the new columns

Change-Id: I439b102e9125c4e6aed988032a21d51b1079f93b
(cherry picked from commit 4aab539192d90a4f5eef801a0d071940e3c325d9)
---
M Hooks.php
M container.php
A db_patches/patch-add-wiki.sql
M flow.sql
M includes/Data/RevisionStorage.php
M includes/Data/UserNameBatch.php
M includes/Model/AbstractRevision.php
M includes/Model/PostRevision.php
M includes/Model/Workflow.php
M includes/WorkflowLoader.php
A maintenance/FlowUpdateUserWiki.php
M tests/PostRevisionTestCase.php
A tests/UserNameListenerTest.php
13 files changed, 470 insertions(+), 43 deletions(-)

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



diff --git a/Hooks.php b/Hooks.php
index 27fef73..f5a36f4 100644
--- a/Hooks.php
+++ b/Hooks.php
@@ -66,6 +66,7 @@
                $updater->modifyExtensionField( 'recentchanges', 'rc_source', 
"$dir/db_patches/patch-rc_source.sql" );
                $updater->modifyExtensionField( 'flow_revision', 
'rev_change_type', "$dir/db_patches/patch-censor_to_suppress.sql" );
                $updater->addExtensionField( 'flow_workflow', 
'workflow_user_ip', "$dir/db_patches/patch-remove_usernames.sql" );
+               $updater->addExtensionField( 'flow_workflow', 
'workflow_user_wiki', "$dir/db_patches/patch-add-wiki.sql" );
 
                require_once 
__DIR__.'/maintenance/FlowInsertDefaultDefinitions.php';
                $updater->addPostDatabaseUpdateMaintenance( 
'FlowInsertDefaultDefinitions' );
@@ -76,6 +77,9 @@
                require_once __DIR__.'/maintenance/FlowSetUserIp.php';
                $updater->addPostDatabaseUpdateMaintenance( 'FlowSetUserIp' );
 
+               require_once __DIR__.'/maintenance/FlowUpdateUserWiki.php';
+               $updater->addPostDatabaseUpdateMaintenance( 
'FlowUpdateUserWiki' );
+
                return true;
        }
 
diff --git a/container.php b/container.php
index 3a466d7..2fe1b6b 100644
--- a/container.php
+++ b/container.php
@@ -170,8 +170,7 @@
        $lifecycle = array(
                new Flow\Data\UserNameListener(
                        $c['repository.username'],
-                       array( 'workflow_user_id' ),
-                       'workflow_wiki'
+                       array( 'workflow_user_id' => 'workflow_user_wiki' )
                ),
                // $c['storage.user_subs.user_index']
        );
@@ -250,11 +249,11 @@
                $c['storage.board_history.index'],
                new Flow\Data\UserNameListener(
                        $c['repository.username'],
-                       array( 'rev_user_id', 'rev_mod_user_id', 
'rev_edit_user_id' ),
-                       null,
-                       // @todo composite wiki id + user columns, for now this
-                       // works as we only display content from this wiki
-                       wfWikiId()
+                       array(
+                               'rev_user_id' => 'rev_user_wiki',
+                               'rev_mod_user_id' => 'rev_mod_user_wiki',
+                               'rev_edit_user_id' => 'rev_edit_user_wiki'
+                       )
                ),
        );
 
@@ -337,11 +336,12 @@
                $c['storage.board_history.index'],
                new Flow\Data\UserNameListener(
                        $c['repository.username'],
-                       array( 'rev_user_id', 'rev_mod_user_id', 
'rev_edit_user_id', 'tree_orig_user_id' ),
-                       null,
-                       // @todo composite wiki id + user columns, for now this
-                       // works as we only display content from this wiki
-                       wfWikiId()
+                       array(
+                               'rev_user_id' => 'rev_user_wiki',
+                               'rev_mod_user_id' => 'rev_mod_user_wiki',
+                               'rev_edit_user_id' => 'rev_edit_user_wiki',
+                               'tree_orig_user_id' => 'tree_orig_user_wiki'
+                       )
                ),
        );
 
diff --git a/db_patches/patch-add-wiki.sql b/db_patches/patch-add-wiki.sql
new file mode 100644
index 0000000..f4f0dc4
--- /dev/null
+++ b/db_patches/patch-add-wiki.sql
@@ -0,0 +1,18 @@
+
+ALTER TABLE /*_*/flow_workflow ADD workflow_user_wiki varchar(32) binary not 
null;
+
+ALTER TABLE /*_*/flow_subscription ADD subscription_user_wiki varchar(32) 
binary not null;
+
+ALTER TABLE /*_*/flow_tree_revision ADD tree_orig_user_wiki varchar(32) binary 
not null;
+
+ALTER TABLE /*_*/flow_revision ADD rev_user_wiki varchar(32) binary not null;
+
+ALTER TABLE /*_*/flow_revision ADD rev_mod_user_wiki varchar(32) binary 
default null;
+
+ALTER TABLE /*_*/flow_revision ADD rev_edit_user_wiki varchar(32) binary 
default null;
+
+DROP INDEX /*i*/flow_subscription_unique_user_workflow ON 
/*_*/flow_subscription;
+CREATE UNIQUE INDEX /*i*/flow_subscription_unique_user_workflow ON 
/*_*/flow_subscription (subscription_workflow_id, subscription_user_id, 
subscription_user_wiki );
+
+DROP INDEX /*i*/flow_subscription_lookup ON /*_*/flow_subscription;
+CREATE INDEX /*i*/flow_subscription_lookup ON /*_*/flow_subscription 
(subscription_user_id, subscription_user_wiki, subscription_last_updated, 
subscription_workflow_id);
diff --git a/flow.sql b/flow.sql
index 1832689..96b5835 100644
--- a/flow.sql
+++ b/flow.sql
@@ -22,6 +22,7 @@
        -- TODO: check what the new global user ids need for storage
        workflow_user_id bigint unsigned not null,
        workflow_user_ip varbinary(39) default null,
+       workflow_user_wiki varchar(32) binary not null,
        -- TODO: is this usefull as a bitfield?  may be premature optimization, 
a string
        -- or list of strings may be simpler and use only a little more space.
        workflow_lock_state int unsigned not null,
@@ -34,12 +35,13 @@
 CREATE TABLE /*_*/flow_subscription (
   subscription_workflow_id int unsigned not null,
   subscription_user_id bigint unsigned not null,
+  subscription_user_wiki varchar(32) binary not null,
   subscription_create_timestamp varchar(14) binary not null,
   subscription_last_updated varchar(14) binary not null
 ) /*$wgDBTableOptions*/;
 
-CREATE UNIQUE INDEX /*i*/flow_subscription_unique_user_workflow ON 
/*_*/flow_subscription (subscription_workflow_id, subscription_user_id);
-CREATE INDEX /*i*/flow_subscription_lookup ON /*_*/flow_subscription 
(subscription_user_id, subscription_last_updated, subscription_workflow_id);
+CREATE UNIQUE INDEX /*i*/flow_subscription_unique_user_workflow ON 
/*_*/flow_subscription (subscription_workflow_id, subscription_user_id, 
subscription_user_wiki );
+CREATE INDEX /*i*/flow_subscription_lookup ON /*_*/flow_subscription 
(subscription_user_id, subscription_user_wiki, subscription_last_updated, 
subscription_workflow_id);
 
 -- TopicList Tables
 CREATE TABLE /*_*/flow_topic_list (
@@ -61,6 +63,7 @@
        tree_orig_create_time varchar(12) binary not null,
        tree_orig_user_id bigint unsigned not null,
        tree_orig_user_ip varbinary(39) default null,
+       tree_orig_user_wiki varchar(32) binary not null,
        -- denormalize post parent as well? Prevents an extra query when 
building
        -- tree from closure table.  unnecessary?
        tree_parent_id binary(11),
@@ -102,6 +105,7 @@
        -- user id creating the revision
        rev_user_id bigint unsigned not null,
        rev_user_ip varbinary(39) default null,
+       rev_user_wiki varchar(32) binary not null,
        -- rev_id of parent or null if no previous revision
        rev_parent_id binary(11) null,
        -- comma separated set of ascii flags.
@@ -116,6 +120,7 @@
        -- moderated by who?
        rev_mod_user_id bigint unsigned,
        rev_mod_user_ip varbinary(39) default null,
+       rev_mod_user_wiki varchar(32) binary default null,
        rev_mod_timestamp varchar(14) binary,
        -- moderated why? (coming soon: how?, where? and what?)
        rev_mod_reason varchar(255) binary,
@@ -124,6 +129,7 @@
        rev_last_edit_id binary(11) null,
        rev_edit_user_id bigint unsigned,
        rev_edit_user_ip varbinary(39) default null,
+       rev_edit_user_wiki varchar(32) binary default null,
 
        PRIMARY KEY (rev_id)
 ) /*$wgDBTableOptions*/;
diff --git a/includes/Data/RevisionStorage.php 
b/includes/Data/RevisionStorage.php
index b5ed926..93cbb53 100644
--- a/includes/Data/RevisionStorage.php
+++ b/includes/Data/RevisionStorage.php
@@ -16,6 +16,7 @@
                'rev_mod_state',
                'rev_mod_user_id',
                'rev_mod_user_ip',
+               'rev_mod_user_wiki',
                'rev_mod_timestamp',
                'rev_mod_reason',
        );
@@ -29,11 +30,6 @@
                'rev_user_text',
                'rev_edit_user_text',
                'rev_mod_user_text',
-               // Delete in the patch that handles these new columns
-               'tree_orig_user_wiki',
-               'rev_user_wiki',
-               'rev_mod_user_wiki',
-               'rev_edit_user_wiki',
        );
 
        protected $externalStores;
diff --git a/includes/Data/UserNameBatch.php b/includes/Data/UserNameBatch.php
index 27d95b5..ce760ff 100644
--- a/includes/Data/UserNameBatch.php
+++ b/includes/Data/UserNameBatch.php
@@ -20,16 +20,14 @@
 
        /**
         * @param UserNameBatch $batch
-        * @param array $keys A list of keys from storage that contain user ids
-        * @param string|null $wikiKey A key from the storage row that contains 
the wiki id.
+        * @param array $keys key - a list of keys from storage that contain 
user ids, value - the wiki for the user id lookup, default to $wiki if null
         * @param string|null $wiki The wikiid to use when $wikiKey is null. If 
both are null wfWikiId() is used
         */
-       public function __construct( UserNameBatch $batch, array $keys, 
$wikiKey = null, $wiki = null ) {
+       public function __construct( UserNameBatch $batch, array $keys, $wiki = 
null ) {
                $this->batch = $batch;
                $this->keys = $keys;
-               if ( $wikiKey !== null ) {
-                       $this->wikiKey = $wikiKey;
-               } elseif ( $wiki === null ) {
+
+               if ( $wiki === null ) {
                        $this->wiki = wfWikiId();
                } else {
                        $this->wiki = $wiki;
@@ -40,17 +38,23 @@
         * Load any user ids in $row into the username batch
         */
        public function onAfterLoad( $object, array $row ) {
-               if ( $this->wikiKey === null ) {
-                       $wiki = $this->wiki;
-               } elseif( isset( $row[$this->wikiKey] ) ) {
-                       $wiki = $row[$this->wikiKey];
-               } else {
-                       wfDebugLog( __CLASS__, __METHOD__ . ": could not detect 
wiki with {$this->wikiKey}" );
-                       return;
-               }
-               foreach ( $this->keys as $key ) {
-                       if ( isset( $row[$key] ) && $row[$key] != 0 ) {
-                               $this->batch->add( $wiki, $row[$key] );
+               foreach ( $this->keys as $userKey => $wikiKey ) {
+                       // check if the user id key exists in the data array 
and 
+                       // make sure it has a non-zero value
+                       if ( isset( $row[$userKey] ) && $row[$userKey] != 0 ) {
+                               // the wiki for the user id lookup is specified,
+                               // check if it exists in the data array
+                               if ( $wikiKey ) {
+                                       if ( !isset( $row[$wikiKey] ) ) {
+                                               wfDebugLog( __CLASS__, 
__METHOD__ . ": could not detect wiki with " . $wikiKey );
+                                               continue;
+                                       }
+                                       $wiki = $row[$wikiKey];
+                               // no wiki lookup is specified, default to 
$this->wiki
+                               } else {
+                                       $wiki = $this->wiki;
+                               }
+                               $this->batch->add( $wiki, $row[$userKey] );
                        }
                }
        }
diff --git a/includes/Model/AbstractRevision.php 
b/includes/Model/AbstractRevision.php
index 18d68e8..c148a08 100644
--- a/includes/Model/AbstractRevision.php
+++ b/includes/Model/AbstractRevision.php
@@ -44,6 +44,11 @@
        protected $userIp;
 
        /**
+        * @var string|null The wiki of the user that created this revision
+        */
+       protected $userWiki;
+
+       /**
         * Array of flags strictly related to the content. Flags are reset when
         * content changes.
         *
@@ -121,6 +126,11 @@
        protected $moderatedByUserIp;
 
        /**
+        * @var string|null The wiki of the user that moderated this revision
+        */
+       protected $moderatedByUserWiki;
+
+       /**
         * @var string|null
         */
        protected $moderatedReason;
@@ -140,6 +150,12 @@
         *     only when $lastEditUserId = 0
         */
        protected $lastEditUserIp;
+
+
+       /**
+        * @var string|null The wiki of the user that most recently changed the 
content
+        */
+       protected $lastEditUserWiki;
 
        /**
         * @param string[] $row
@@ -162,6 +178,7 @@
                } elseif ( isset( $row['rev_user_text'] ) && $obj->userId === 0 
) {
                        $obj->userIp = $row['rev_user_text'];
                }
+               $obj->userWiki = isset( $row['rev_user_wiki'] ) ? 
$row['rev_user_wiki'] : '';
                $obj->prevRevision = UUID::create( $row['rev_parent_id'] );
                $obj->changeType = $row['rev_change_type'];
                $obj->flags = array_filter( explode( ',', $row['rev_flags'] ) );
@@ -178,6 +195,7 @@
                } elseif ( isset( $row['rev_mod_user_text'] ) && 
$obj->moderatedByUserId === 0 ) {
                        $obj->moderatedByUserIp = $row['rev_mod_user_text'];
                }
+               $obj->moderatedByUserWiki = isset( $row['rev_mod_user_wiki'] ) 
? $row['rev_mod_user_wiki'] : null;
                $obj->moderationTimestamp = $row['rev_mod_timestamp'];
                $obj->moderatedReason = isset( $row['rev_mod_reason'] ) ? 
$row['rev_mod_reason'] : null;
 
@@ -196,6 +214,7 @@
                        $obj->lastEditUserIp = $row['rev_edit_user_text'];
                }
                $obj->lastEditUserIp = isset( $row['rev_edit_user_ip'] ) ? 
$row['rev_edit_user_ip'] : null;
+               $obj->lastEditUserWiki = isset( $row['rev_edit_user_wiki'] ) ? 
$row['rev_edit_user_wiki'] : null;
 
                return $obj;
        }
@@ -209,6 +228,7 @@
                        'rev_id' => $obj->revId->getBinary(),
                        'rev_user_id' => $obj->userId,
                        'rev_user_ip' => $obj->userIp,
+                       'rev_user_wiki' => $obj->userWiki,
                        'rev_parent_id' => $obj->prevRevision ? 
$obj->prevRevision->getBinary() : null,
                        'rev_change_type' => $obj->changeType,
                        'rev_type' => $obj->getRevisionType(),
@@ -220,12 +240,14 @@
                        'rev_mod_state' => $obj->moderationState,
                        'rev_mod_user_id' => $obj->moderatedByUserId,
                        'rev_mod_user_ip' => $obj->moderatedByUserIp,
+                       'rev_mod_user_wiki' => $obj->moderatedByUserWiki,
                        'rev_mod_timestamp' => $obj->moderationTimestamp,
                        'rev_mod_reason' => $obj->moderatedReason,
 
                        'rev_last_edit_id' => $obj->lastEditId ? 
$obj->lastEditId->getBinary() : null,
                        'rev_edit_user_id' => $obj->lastEditUserId,
                        'rev_edit_user_ip' => $obj->lastEditUserIp,
+                       'rev_edit_user_wiki' => $obj->lastEditUserWiki,
                );
        }
 
@@ -244,7 +266,7 @@
                }
                $obj = clone $this;
                $obj->revId = UUID::create();
-               list( $obj->userId, $obj->userIp ) = self::userFields( $user );
+               list( $obj->userId, $obj->userIp, $obj->userWiki ) = 
self::userFields( $user );
                $obj->prevRevision = $this->revId;
                $obj->changeType = '';
                return $obj;
@@ -291,14 +313,16 @@
                $obj->moderatedReason = $reason;
                $obj->moderationState = $state;
 
-               list( $userId, $userIp ) = self::userFields( $user );
+               list( $userId, $userIp, $userWiki ) = self::userFields( $user );
                if ( $state === self::MODERATED_NONE ) {
                        $obj->moderatedByUserId = null;
                        $obj->moderatedByUserIp = null;
+                       $obj->moderatedByUserWiki = null;
                        $obj->moderationTimestamp = null;
                } else {
                        $obj->moderatedByUserId = $userId;
                        $obj->moderatedByUserIp = $userIp;
+                       $obj->moderatedByUserWiki = $userWiki;
                        $obj->moderationTimestamp = wfTimestampNow();
                }
 
@@ -406,6 +430,10 @@
                return $this->userIp;
        }
 
+       public function getUserWiki() {
+               return $this->userWiki;
+       }
+
        /**
         * Should only be used for setting the initial content.  To set 
subsequent content
         * use self::setNextContent
@@ -460,7 +488,7 @@
                        $this->content = null;
                        $this->setContent( $content );
                        $this->lastEditId = $this->getRevisionId();
-                       list( $this->lastEditUserId, $this->lastEditUserIp ) = 
self::userFields( $user );
+                       list( $this->lastEditUserId, $this->lastEditUserIp, 
$this->lastEditUserWiki ) = self::userFields( $user );
                }
        }
 
@@ -615,6 +643,13 @@
        }
 
        /**
+        * @return string|null
+        */
+       public function getLastContentEditUserWiki() {
+               return $this->lastEditUserWiki;
+       }
+
+       /**
         * @return integer|null
         */
        public function getModeratedByUserId() {
@@ -628,8 +663,12 @@
                return $this->moderatedByUserIp;
        }
 
+       public function getModeratedByUserWiki() {
+               return $this->moderatedByUserWiki;
+       }
+
        /**
-        * Return a (userId, userIp) tuple for the given
+        * Return a (userId, userIp, wikiId) tuple for the given
         * user object.  userIp is null for userId != 0
         *
         * @param User $user
@@ -639,11 +678,13 @@
                if ( $user->isAnon() ) {
                        $userId = 0;
                        $userIp = $user->getName();
+                       $userWiki = wfWikiId();
                } else {
                        $userId = $user->getId();
                        $userIp = null;
+                       $userWiki = wfWikiId();
                }
-               return array( $userId, $userIp );
+               return array( $userId, $userIp, $userWiki );
        }
 
        /**
diff --git a/includes/Model/PostRevision.php b/includes/Model/PostRevision.php
index 5fd84e3..e9ddef4 100644
--- a/includes/Model/PostRevision.php
+++ b/includes/Model/PostRevision.php
@@ -32,6 +32,11 @@
        protected $origUserIp;
 
        /**
+        * @var string
+        */
+       protected $origUserWiki;
+
+       /**
         * @var UUID|null
         */
        protected $replyToId;
@@ -131,6 +136,7 @@
                $obj->postId = $uuid;
                $obj->origUserId = $obj->userId = $user->getId();
                $obj->origUserIp = $obj->userIp = $user->getName();
+               $obj->origUserWiki = $obj->userWiki = wfWikiId();
                $obj->origCreateTime = wfTimestampNow();
                $obj->setReplyToId( null ); // not a reply to anything
                $obj->prevRevision = null; // no parent revision
@@ -162,6 +168,7 @@
                } elseif ( isset( $row['tree_orig_user_text'] ) && 
$obj->origUserId === 0 ) {
                        $obj->origUserIp = $row['tree_orig_user_text'];
                }
+               $obj->origUserWiki = isset( $row['tree_orig_user_wiki'] ) ? 
$row['tree_orig_user_wiki'] : '';
                return $obj;
        }
 
@@ -178,6 +185,7 @@
                        'tree_orig_create_time' => $rev->origCreateTime,
                        'tree_orig_user_id' => $rev->origUserId,
                        'tree_orig_user_ip' => $rev->origUserIp,
+                       'tree_orig_user_wiki' => $rev->origUserWiki,
                );
        }
 
@@ -191,9 +199,10 @@
                $reply = new self;
                // No great reason to create two uuid's,  a post and its first 
revision can share a uuid
                $reply->revId = $reply->postId = UUID::create();
-               list( $reply->userId, $reply->userIp ) = self::userFields( 
$user );
+               list( $reply->userId, $reply->userIp, $reply->userWiki ) = 
self::userFields( $user );
                $reply->origUserId = $reply->userId;
                $reply->origUserIp = $reply->userIp;
+               $reply->origUserWiki = wfWikiId();
                $reply->origCreateTime = wfTimestampNow();
                $reply->replyToId = $this->postId;
                $reply->setContent( $content );
@@ -221,6 +230,10 @@
                return $this->origUserId;
        }
 
+       public function getCreatorWiki() {
+               return $this->origUserWiki;
+       }
+
        /**
         * Get the user ip of the user who created this post if it
         * was created by an anonymous user
diff --git a/includes/Model/Workflow.php b/includes/Model/Workflow.php
index 3be4cba..edc964f 100644
--- a/includes/Model/Workflow.php
+++ b/includes/Model/Workflow.php
@@ -52,6 +52,11 @@
        protected $userIp;
 
        /**
+        * @var string
+        */
+       protected $userWiki;
+
+       /**
         * lock state is a list of state updates, the final state
         * is the active state. It is unused and must be reviewed
         * before any use
@@ -97,6 +102,7 @@
                } elseif ( isset( $row['workflow_user_text'] ) && $obj->userId 
=== 0 ) {
                        $obj->userIp = $row['workflow_user_text'];
                }
+               $obj->userWiki = isset( $row['workflow_user_wiki'] ) ? 
$row['workflow_user_wiki'] : '';
                $obj->lockState = $row['workflow_lock_state'];
                $obj->definitionId = UUID::create( 
$row['workflow_definition_id'] );
                $obj->lastModified = $row['workflow_last_update_timestamp'];
@@ -116,6 +122,7 @@
                        'workflow_title_text' => $obj->titleText,
                        'workflow_user_id' => $obj->userId,
                        'workflow_user_ip' => $obj->userIp,
+                       'workflow_user_wiki' => $obj->userWiki,
                        'workflow_lock_state' => $obj->lockState,
                        'workflow_definition_id' => 
$obj->definitionId->getBinary(),
                        'workflow_last_update_timestamp' => $obj->lastModified,
@@ -148,7 +155,7 @@
                $obj->pageId = $title->getArticleID();
                $obj->namespace = $title->getNamespace();
                $obj->titleText = $title->getDBkey();
-               list( $obj->userId, $obj->userIp ) = 
AbstractRevision::userFields( $user );
+               list( $obj->userId, $obj->userIp, $obj->userWiki ) = 
AbstractRevision::userFields( $user );
                $obj->lockState = 0;
                $obj->definitionId = $definition->getId();
                $obj->updateLastModified();
@@ -198,6 +205,11 @@
        /**
         * @return string
         */
+       public function getUserWiki() { return $this->userWiki; }
+
+       /**
+        * @return string
+        */
        public function getLastModified() { return $this->lastModified; }
 
        /**
diff --git a/includes/WorkflowLoader.php b/includes/WorkflowLoader.php
index c01c059..b4d9a0b 100644
--- a/includes/WorkflowLoader.php
+++ b/includes/WorkflowLoader.php
@@ -78,6 +78,7 @@
                if ( !$definition->getOption( 'unique' ) ) {
                        throw new InvalidDataException( 'Workflow is 
non-unique, can only fetch object by title + id', 'fail-load-data' );
                }
+
                $found = $storage->find( array(
                        'workflow_definition_id' => $definition->getId(),
                        'workflow_wiki' => $title->isLocal() ? wfWikiId() : 
$title->getTransWikiID(),
diff --git a/maintenance/FlowUpdateUserWiki.php 
b/maintenance/FlowUpdateUserWiki.php
new file mode 100644
index 0000000..6b2571c
--- /dev/null
+++ b/maintenance/FlowUpdateUserWiki.php
@@ -0,0 +1,277 @@
+<?php
+
+use Flow\Container;
+use Flow\Model\UUID;
+use Flow\Model\Header;
+use Flow\Model\PostRevision;
+
+$IP = getenv( 'MW_INSTALL_PATH' );
+if ( $IP === false ) {
+       $IP = dirname( __FILE__ ) . '/../../..';
+}
+require_once( "$IP/maintenance/Maintenance.php" );
+
+/**
+ * Update all xxx_user_wiki field to have the correct wiki name
+ *
+ * @ingroup Maintenance
+ */
+class FlowUpdateUserWiki extends LoggedUpdateMaintenance {
+
+       /**
+        * Use to track the number of updated count
+        */
+       private $updatedCount = 0;
+
+       public function __construct() {
+               parent::__construct();
+               $this->mDescription = "Update xxx_user_wiki field in tables: 
flow_workflow, flow_tree_revision, flow_revision";
+               $this->setBatchSize( 300 );
+       }
+
+       /**
+        * This is a top-to-bottom update, the process is like this:
+        * workflow -> header -> header revision -> history
+        * workflow -> topic list -> post tree revision -> post revision -> 
history
+        *
+        * Some side effect, the script will also update those *_user_wiki 
fields with
+        * empty *_user_id and *_user_ip, but this doesn't hurt. Alternatively, 
we could
+        * add a check user_id != 0 and user_ip is not null to the query, but 
this will
+        * result in more db queries
+        *
+        */
+       protected function doDBUpdates() {
+               $id = '';
+               $count = $this->mBatchSize;
+               $dbr = Container::get( 'db.factory' )->getDB( DB_SLAVE );
+
+               while ( $count == $this->mBatchSize ) {
+                       $count = 0;
+                       $res = $dbr->select(
+                               array( 'flow_workflow', 'flow_definition' ),
+                               array( 'workflow_wiki', 'workflow_id', 
'definition_type' ),
+                               array(
+                                       'workflow_id > ' . $dbr->addQuotes( $id 
),
+                                       'workflow_definition_id = definition_id'
+                               ),
+                               __METHOD__,
+                               array( 'ORDER BY' => 'workflow_id ASC', 'LIMIT' 
=> $this->mBatchSize )
+                       );
+                       if ( $res ) {
+                               foreach ( $res as $row ) {
+                                       $count++;
+                                       $id = $row->workflow_id;
+                                       $uuid = UUID::create( $row->workflow_id 
);
+                                       $workflow = Container::get( 
'storage.workflow' )->get( $uuid );
+                                       if ( $workflow ) {
+                                               // definition type 'topic' is 
always under a 'discussion' and they
+                                               // will be handled while 
processing 'discussion'
+                                               if ( $row->definition_type == 
'discussion' ) {
+                                                       $this->updateHeader( 
$workflow, $row->workflow_wiki );
+                                                       $this->updateTopicList( 
$workflow, $row->workflow_wiki );
+                                               }
+                                               $this->updateWorkflow( 
$workflow, $row->workflow_wiki );
+                                       }
+                               }
+                               $this->output( "processed $count records in " . 
__METHOD__ . "\n" );
+                       } else {
+                               throw new \MWException( 'SQL error in 
maintenance script ' . __CLASS__ . '::' . __METHOD__ );
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * Update workflow
+        */
+       private function updateWorkflow( $wf, $wiki ) {
+               $dbw = Container::get( 'db.factory' )->getDB( DB_MASTER );
+               $res = $dbw->update(
+                       'flow_workflow',
+                       array( 'workflow_user_wiki' => $wiki ),
+                       array( 'workflow_id' => $wf->getId()->getBinary() )
+               );
+               if ( !$res ) {
+                       throw new \MWException( 'SQL error in maintenance 
script ' . __CLASS__ . '::' . __METHOD__ );
+               }
+
+               $this->output( "processing workflow: " . $wf->getId()->getHex() 
. ' in ' . __METHOD__ . "\n" );
+               $this->checkForSlave();
+       }
+
+       /**
+        * Update header
+        */
+       private function updateHeader( $workflow, $wiki ) {
+               $id = '';
+               $count = $this->mBatchSize;
+               $dbr = Container::get( 'db.factory' )->getDB( DB_SLAVE );
+
+               while ( $count == $this->mBatchSize ) {
+                       $count = 0;
+                       $res = $dbr->select(
+                               array( 'flow_header_revision', 'flow_revision' 
),
+                               array( 'rev_id', 'rev_type' ),
+                               array(
+                                       'rev_id > ' . $dbr->addQuotes( $id ),
+                                       'header_rev_id = rev_id',
+                                       'header_workflow_id' => 
$workflow->getId()->getBinary()
+                               ),
+                               __METHOD__,
+                               array( 'ORDER BY' => 'header_rev_id ASC', 
'LIMIT' => $this->mBatchSize )
+                       );
+                       if ( $res ) {
+                               foreach ( $res as $row ) {
+                                       $count++;
+                                       $id = $row->rev_id;
+                                       $revision = Container::get( 
'storage.header' )->get( UUID::create( $row->rev_id ) );
+                                       if ( $revision ) {
+                                               $this->updateHistory( 
$revision, $wiki );
+                                               $this->updateRevision( 
$revision, $wiki );
+                                       }
+                               }
+                       } else {
+                               throw new \MWException( 'SQL error in 
maintenance script ' . __CLASS__ . '::' . __METHOD__ );
+                       }
+
+                       $this->output( "processed $count records in " . 
__METHOD__ . "\n" );
+               }
+       }
+
+       /**
+        * Update topic list
+        */
+       private function updateTopicList( $workflow, $wiki ) {
+               $id = '';
+               $count = $this->mBatchSize;
+               $dbr = Container::get( 'db.factory' )->getDB( DB_SLAVE );
+
+               while ( $count == $this->mBatchSize ) {
+                       $count = 0;
+                       $res = $dbr->select(
+                               array( 'flow_topic_list' ),
+                               array( 'topic_id' ),
+                               array(
+                                       'topic_list_id' => 
$workflow->getId()->getBinary(),
+                                       'topic_id > ' . $dbr->addQuotes( $id ),
+                               ),
+                               __METHOD__,
+                               array( 'ORDER BY' => 'topic_id ASC', 'LIMIT' => 
$this->mBatchSize )
+                       );
+                       if ( $res ) {
+                               $index = 0;
+                               foreach ( $res as $row ) {
+                                       $count++;
+                                       $index++;
+                                       $id = $row->topic_id;
+                                       $post = Container::get( 
'loader.root_post' )->get( UUID::create( $row->topic_id ) );
+                                       if ( $post ) {
+                                               $this->updatePost( $post, $wiki 
);
+                                       }
+                               }
+                               $this->output( "processed $index topics in " . 
__METHOD__ . "\n" );
+                       } else {
+                               throw new \MWException( 'SQL error in 
maintenance script ' . __CLASS__ . '::' . __METHOD__ );
+                       }
+                       $this->output( "processed $count records in " . 
__METHOD__ . "\n" );
+               }
+       }
+
+       /**
+        * Update post
+        */
+       private function updatePost( $post, $wiki ) {
+               $this->updateHistory( $post, $wiki );
+               $this->updateRevision( $post, $wiki );
+               foreach ( $post->getChildren() as $child ) {
+                       $this->updatePost( $child, $wiki );
+               }
+       }
+
+       /**
+        * Update history revision
+        */
+       private function updateHistory( $post, $wiki ) {
+               if ( $post->getPrevRevisionId() ) {
+                       $parent = null;
+                       if ( $post->getRevisionType() === 'header' ) {
+                               $parent = Container::get( 'storage.header' 
)->get( UUID::create( $post->getPrevRevisionId() ) );
+                       } elseif ( $post->getRevisionType() === 'post' ) {
+                               $parent = Container::get( 'storage.post' 
)->get( UUID::create( $post->getPrevRevisionId() ) );
+                       }
+                       if ( $parent ) {
+                               $this->updateRevision( $parent, $wiki );
+                               $this->updateHistory( $parent, $wiki );
+                       }
+               }
+       }
+
+       /**
+        * Update either header or post revision
+        */
+       private function updateRevision( $revision, $wiki ) {
+               if ( !$revision ) {
+                       return;
+               }
+               $type = $revision->getRevisionType();
+
+               $dbw = Container::get( 'db.factory' )->getDB( DB_MASTER );
+               $res = $dbw->update(
+                       'flow_revision',
+                       array(
+                               'rev_user_wiki' => $wiki,
+                               'rev_mod_user_wiki' => $wiki,
+                               'rev_edit_user_wiki' => $wiki,
+                       ),
+                       array(
+                               'rev_id' => 
$revision->getRevisionId()->getBinary(),
+                       ),
+                       __METHOD__
+               );
+               if ( !$res ) {
+                       throw new \MWException( 'SQL error in maintenance 
script ' . __CLASS__ . '::' . __METHOD__ );
+               }
+
+               if ( $type === 'post' ) {
+                       $res = $dbw->update(
+                               'flow_tree_revision',
+                               array(
+                                       'tree_orig_user_wiki' => $wiki,
+                               ),
+                               array(
+                                       'tree_rev_id' => 
$revision->getRevisionId()->getBinary(),
+                               ),
+                               __METHOD__
+                       );
+                       if ( !$res ) {
+                               throw new \MWException( 'SQL error in 
maintenance script ' . __CLASS__ . '::' . __METHOD__ );
+                       }
+               }
+
+               $this->output( "processing $type: " . 
$revision->getRevisionId()->getHex() . ' in ' . __METHOD__ . "\n" );
+               $this->checkForSlave();
+       }
+
+       private function checkForSlave() {
+               global $wgFlowCluster;
+
+               $this->updatedCount++;
+               if ( $this->updatedCount > $this->mBatchSize ) {
+                       wfWaitForSlaves( false, false, $wgFlowCluster );
+                       $this->updatedCount = 0;
+               }
+       }
+
+       /**
+        * Get the update key name to go in the update log table
+        *
+        * @return string
+        */
+       protected function getUpdateKey() {
+               return 'FlowUpdateUserWiki';
+       }
+}
+
+$maintClass = "FlowUpdateUserWiki";
+require_once( DO_MAINTENANCE );
diff --git a/tests/PostRevisionTestCase.php b/tests/PostRevisionTestCase.php
index dcb6b39..5ece0e3 100644
--- a/tests/PostRevisionTestCase.php
+++ b/tests/PostRevisionTestCase.php
@@ -60,6 +60,9 @@
                        'rev_last_edit_id' => null,
                        'rev_edit_user_id' => null,
                        'rev_edit_user_ip' => null,
+                       'rev_user_wiki' => wfWikiId(),
+                       'rev_mod_user_wiki' => null,
+                       'rev_edit_user_wiki' => null,
 
                        // flow_tree_revision
                        'tree_rev_descendant_id' => $uuidPost->getBinary(),
@@ -68,6 +71,7 @@
                        'tree_orig_user_id' => $userId,
                        'tree_orig_user_ip' => $userIp,
                        'tree_parent_id' => null,
+                       'tree_orig_user_wiki' => wfWikiId(),
                );
        }
 
diff --git a/tests/UserNameListenerTest.php b/tests/UserNameListenerTest.php
new file mode 100644
index 0000000..32b7cbd
--- /dev/null
+++ b/tests/UserNameListenerTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Flow\Tests;
+
+use Closure;
+use ReflectionClass;
+use Flow\Container;
+use Flow\Data\UserNameBatch;
+use Flow\Data\UserNameListener;
+
+/**
+ * @group Database
+ * @group Flow
+ */
+class UserNameListenerTest extends \MediaWikiTestCase {
+
+       public function onAfterLoadDataProvider() {
+               return array (
+                       array( array( 'user_id' => '1', 'user_wiki' => 'frwiki' 
), array( 'user_id' => 'user_wiki' ), 'frwiki', 'enwiki' ),
+                       array( array( 'user_id' => '2' ), array( 'user_id' => 
null ), 'enwiki', 'enwiki' ),
+                       array( array( 'user_id' => '3' ), array( 'user_id' => 
'user_wiki' ), null ),
+                       // Use closure because wfWikiId() in testxxx() 
functions appends -unittest_ at the end
+                       array( array( 'user_id' => '4' ), array( 'user_id' => 
null ), function() { return wfWikiId(); } ),
+               );
+       }
+
+       /**
+        * @dataProvider onAfterLoadDataProvider
+        */
+       public function testOnAfterLoad( array $row, array $key, $expectedWiki, 
$defaultWiki = null ) {
+               $batch = new UserNameBatch( $this->getMock( 
'\Flow\Data\UsernameQuery' ) );
+               $listener = new UserNameListener( $batch, $key, $defaultWiki );
+               $listener->onAfterLoad( (object)$row, $row );
+
+               $reflection = new ReflectionClass( $batch );
+               $prop = $reflection->getProperty( 'queued' );
+               $prop->setAccessible( true );
+               $queued = $prop->getValue( $batch );
+
+               if ( $expectedWiki instanceof Closure ) {
+                       $expectedWiki = call_user_func( $expectedWiki );
+               }
+
+               if ( $expectedWiki ) {
+                       $this->assertTrue( in_array( $row['user_id'], 
$queued[$expectedWiki] ) );
+               } else {
+                       $this->assertEmpty( $queued );
+               }
+       }
+
+}

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I439b102e9125c4e6aed988032a21d51b1079f93b
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: wmf/1.23wmf16
Gerrit-Owner: EBernhardson <ebernhard...@wikimedia.org>
Gerrit-Reviewer: Bsitu <bs...@wikimedia.org>
Gerrit-Reviewer: EBernhardson <ebernhard...@wikimedia.org>
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