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