Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
RenameUserJob
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 2
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2
3namespace MediaWiki\RenameUser;
4
5use InvalidArgumentException;
6use Job;
7use MediaWiki\Config\Config;
8use MediaWiki\MainConfigNames;
9use MediaWiki\Title\Title;
10use Wikimedia\Rdbms\ILBFactory;
11
12/**
13 * Custom job to perform updates on tables in busier environments
14 *
15 * Job parameters include:
16 *   - table     : DB table to update
17 *   - column    : The *_user_text column to update
18 *   - oldname   : The old user name
19 *   - newname   : The new user name
20 *   - count     : The expected number of rows to update in this batch
21 *
22 * Additionally, one of the following groups of parameters must be set:
23 * a) The timestamp based rename parameters:
24 *   - timestampColumn : The *_timestamp column
25 *   - minTimestamp    : The minimum bound of the timestamp column range for this batch
26 *   - maxTimestamp    : The maximum bound of the timestamp column range for this batch
27 *   - uniqueKey       : A column that is unique (preferably the PRIMARY KEY) [optional]
28 * b) The unique key based rename parameters:
29 *   - uniqueKey : A column that is unique (preferably the PRIMARY KEY)
30 *   - keyId     : A list of values for this column to determine rows to update for this batch
31 *
32 * To avoid some race conditions, the following parameters should be set:
33 *   - userID    : The ID of the user to update
34 *   - uidColumn : The *_user_id column
35 */
36class RenameUserJob extends Job {
37    /** @var int */
38    private $updateRowsPerQuery;
39
40    /** @var ILBFactory */
41    private $lbFactory;
42
43    public function __construct(
44        Title $title,
45        $params,
46        Config $config,
47        ILBFactory $lbFactory
48    ) {
49        parent::__construct( 'renameUser', $title, $params );
50
51        $this->updateRowsPerQuery = $config->get( MainConfigNames::UpdateRowsPerQuery );
52        $this->lbFactory = $lbFactory;
53    }
54
55    public function run() {
56        $dbw = $this->lbFactory->getPrimaryDatabase();
57        $ticket = $this->lbFactory->getEmptyTransactionTicket( __METHOD__ );
58        $table = $this->params['table'];
59        $column = $this->params['column'];
60
61        $oldname = $this->params['oldname'];
62        $newname = $this->params['newname'];
63        if ( isset( $this->params['userID'] ) ) {
64            $userID = $this->params['userID'];
65            $uidColumn = $this->params['uidColumn'];
66        } else {
67            $userID = null;
68            $uidColumn = null;
69        }
70        if ( isset( $this->params['timestampColumn'] ) ) {
71            $timestampColumn = $this->params['timestampColumn'];
72            $minTimestamp = $this->params['minTimestamp'];
73            $maxTimestamp = $this->params['maxTimestamp'];
74        } else {
75            $timestampColumn = null;
76            $minTimestamp = null;
77            $maxTimestamp = null;
78        }
79        $uniqueKey = $this->params['uniqueKey'] ?? null;
80        $keyId = $this->params['keyId'] ?? null;
81
82        # Conditions like "*_user_text = 'x'
83        $conds = [ $column => $oldname ];
84        # If user ID given, add that to condition to avoid rename collisions
85        if ( $userID !== null ) {
86            $conds[$uidColumn] = $userID;
87        }
88        # Bound by timestamp if given
89        if ( $timestampColumn !== null ) {
90            $conds[] = $dbw->expr( $timestampColumn, '>=', $minTimestamp );
91            $conds[] = $dbw->expr( $timestampColumn, '<=', $maxTimestamp );
92        # Bound by unique key if given (B/C)
93        } elseif ( $uniqueKey !== null && $keyId !== null ) {
94            $conds[$uniqueKey] = $keyId;
95        } else {
96            throw new InvalidArgumentException( 'Expected ID batch or time range' );
97        }
98
99        # Actually update the rows for this job...
100        if ( $uniqueKey !== null ) {
101            // Select the rows to update by PRIMARY KEY
102            $ids = $dbw->newSelectQueryBuilder()
103                ->select( $uniqueKey )
104                ->from( $table )
105                ->where( $conds )
106                ->caller( __METHOD__ )->fetchFieldValues();
107            # Update these rows by PRIMARY KEY to avoid replica lag
108            foreach ( array_chunk( $ids, $this->updateRowsPerQuery ) as $batch ) {
109                $this->lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
110
111                $dbw->newUpdateQueryBuilder()
112                    ->update( $table )
113                    ->set( [ $column => $newname ] )
114                    ->where( [ $column => $oldname, $uniqueKey => $batch ] )
115                    ->caller( __METHOD__ )->execute();
116            }
117        } else {
118            # Update the chunk of rows directly
119            $dbw->newUpdateQueryBuilder()
120                ->update( $table )
121                ->set( [ $column => $newname ] )
122                ->where( $conds )
123                ->caller( __METHOD__ )->execute();
124        }
125
126        return true;
127    }
128}
129/** @deprecated class alias since 1.43 */
130class_alias( RenameUserJob::class, 'RenameUserJob' );