MediaWiki  1.34.0
RenameUserJob.php
Go to the documentation of this file.
1 <?php
2 
28 class RenameUserJob extends Job {
30  private static $actorMigratedColumns = [
31  'revision.rev_user_text',
32  'archive.ar_user_text',
33  'ipblocks.ipb_by_text',
34  'image.img_user_text',
35  'oldimage.oi_user_text',
36  'filearchive.fa_user_text',
37  'recentchanges.rc_user_text',
38  'logging.log_user_text',
39  ];
40 
41  public function __construct( Title $title, $params = [] ) {
42  parent::__construct( 'renameUser', $title, $params );
43  }
44 
45  public function run() {
46  global $wgUpdateRowsPerQuery;
47 
48  $dbw = wfGetDB( DB_MASTER );
49  $table = $this->params['table'];
50  $column = $this->params['column'];
51 
52  // Skip core tables that were migrated to the actor table, even if the
53  // field still exists in the database.
54  if ( in_array( "$table.$column", self::$actorMigratedColumns, true ) ) {
56  wfDebugLog( 'Renameuser',
57  "Ignoring job {$this->toString()}, column $table.$column "
58  . "actor migration stage lacks WRITE_OLD\n"
59  );
60  return true;
61  }
62  }
63 
64  // It's not worth a hook to let extensions add themselves to that list.
65  // Just check whether the table and column still exist instead.
66  if ( !$dbw->tableExists( $table, __METHOD__ ) ) {
67  wfDebugLog( 'Renameuser',
68  "Ignoring job {$this->toString()}, table $table does not exist\n"
69  );
70  return true;
71  } elseif ( !$dbw->fieldExists( $table, $column, __METHOD__ ) ) {
72  wfDebugLog( 'Renameuser',
73  "Ignoring job {$this->toString()}, column $table.$column does not exist\n"
74  );
75  return true;
76  }
77 
78  $oldname = $this->params['oldname'];
79  $newname = $this->params['newname'];
80  $count = $this->params['count'];
81  if ( isset( $this->params['userID'] ) ) {
82  $userID = $this->params['userID'];
83  $uidColumn = $this->params['uidColumn'];
84  } else {
85  $userID = null;
86  $uidColumn = null;
87  }
88  if ( isset( $this->params['timestampColumn'] ) ) {
89  $timestampColumn = $this->params['timestampColumn'];
90  $minTimestamp = $this->params['minTimestamp'];
91  $maxTimestamp = $this->params['maxTimestamp'];
92  } else {
93  $timestampColumn = null;
94  $minTimestamp = null;
95  $maxTimestamp = null;
96  }
97  $uniqueKey = $this->params['uniqueKey'] ?? null;
98  $keyId = $this->params['keyId'] ?? null;
99  $logId = $this->params['logId'] ?? null;
100 
101  if ( $logId ) {
102  # Block until the transaction that inserted this job commits.
103  # The atomic section is for sanity as FOR UPDATE does not lock in auto-commit mode
104  # per http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html.
105  $dbw->startAtomic( __METHOD__ );
106  $committed = $dbw->selectField( 'logging',
107  '1',
108  [ 'log_id' => $logId ],
109  __METHOD__,
110  [ 'FOR UPDATE' ]
111  );
112  $dbw->endAtomic( __METHOD__ );
113  # If the transaction inserting this job was rolled back, detect that
114  if ( $committed === false ) { // rollback happened?
115  throw new LogicException( 'Cannot run job if the account rename failed.' );
116  }
117  }
118 
119  # Flush any state snapshot data (and release the lock above)
120  $dbw->commit( __METHOD__, 'flush' );
121 
122  # Conditions like "*_user_text = 'x'
123  $conds = [ $column => $oldname ];
124  # If user ID given, add that to condition to avoid rename collisions
125  if ( $userID !== null ) {
126  $conds[$uidColumn] = $userID;
127  }
128  # Bound by timestamp if given
129  if ( $timestampColumn !== null ) {
130  $conds[] = "$timestampColumn >= " . $dbw->addQuotes( $minTimestamp );
131  $conds[] = "$timestampColumn <= " . $dbw->addQuotes( $maxTimestamp );
132  # Bound by unique key if given (B/C)
133  } elseif ( $uniqueKey !== null && $keyId !== null ) {
134  $conds[$uniqueKey] = $keyId;
135  } else {
136  throw new InvalidArgumentException( 'Expected ID batch or time range' );
137  }
138 
139  $affectedCount = 0;
140  # Actually update the rows for this job...
141  if ( $uniqueKey !== null ) {
142  # Select the rows to update by PRIMARY KEY
143  $ids = $dbw->selectFieldValues( $table, $uniqueKey, $conds, __METHOD__ );
144  # Update these rows by PRIMARY KEY to avoid slave lag
145  foreach ( array_chunk( $ids, $wgUpdateRowsPerQuery ) as $batch ) {
146  $dbw->commit( __METHOD__, 'flush' );
147  wfWaitForSlaves();
148 
149  $dbw->update( $table,
150  [ $column => $newname ],
151  [ $column => $oldname, $uniqueKey => $batch ],
152  __METHOD__
153  );
154  $affectedCount += $dbw->affectedRows();
155  }
156  } else {
157  # Update the chunk of rows directly
158  $dbw->update( $table,
159  [ $column => $newname ],
160  $conds,
161  __METHOD__
162  );
163  $affectedCount += $dbw->affectedRows();
164  }
165 
166  # Special case: revisions may be deleted while renaming...
167  if ( $affectedCount < $count && $table === 'revision' && $timestampColumn !== null ) {
168  # If some revisions were not renamed, they may have been deleted.
169  # Do a pass on the archive table to get these straglers...
170  $ids = $dbw->selectFieldValues(
171  'archive',
172  'ar_id',
173  [
174  'ar_user_text' => $oldname,
175  'ar_user' => $userID,
176  // No user,rev_id index, so use timestamp to bound
177  // the rows. This can use the user,timestamp index.
178  "ar_timestamp >= '$minTimestamp'",
179  "ar_timestamp <= '$maxTimestamp'"
180  ],
181  __METHOD__
182  );
183  foreach ( array_chunk( $ids, $wgUpdateRowsPerQuery ) as $batch ) {
184  $dbw->commit( __METHOD__, 'flush' );
185  wfWaitForSlaves();
186 
187  $dbw->update(
188  'archive',
189  [ 'ar_user_text' => $newname ],
190  [ 'ar_user_text' => $oldname, 'ar_id' => $batch ],
191  __METHOD__
192  );
193  }
194  }
195  # Special case: revisions may be restored while renaming...
196  if ( $affectedCount < $count && $table === 'archive' && $timestampColumn !== null ) {
197  # If some revisions were not renamed, they may have been restored.
198  # Do a pass on the revision table to get these straglers...
199  $ids = $dbw->selectFieldValues(
200  'revision',
201  'rev_id',
202  [
203  'rev_user_text' => $oldname,
204  'rev_user' => $userID,
205  // No user,rev_id index, so use timestamp to bound
206  // the rows. This can use the user,timestamp index.
207  "rev_timestamp >= '$minTimestamp'",
208  "rev_timestamp <= '$maxTimestamp'"
209  ],
210  __METHOD__
211  );
212  foreach ( array_chunk( $ids, $wgUpdateRowsPerQuery ) as $batch ) {
213  $dbw->commit( __METHOD__, 'flush' );
214  wfWaitForSlaves();
215 
216  $dbw->update(
217  'revision',
218  [ 'rev_user_text' => $newname ],
219  [ 'rev_user_text' => $oldname, 'rev_id' => $batch ],
220  __METHOD__
221  );
222  }
223  }
224 
225  return true;
226  }
227 }
RenameUserJob\__construct
__construct(Title $title, $params=[])
Definition: RenameUserJob.php:41
RenameUserJob
Custom job to perform updates on tables in busier environments.
Definition: RenameUserJob.php:28
Job\$title
Title $title
Definition: Job.php:41
Job\$params
array $params
Array of job parameters.
Definition: Job.php:35
wfWaitForSlaves
wfWaitForSlaves( $ifWritesSince=null, $wiki=false, $cluster=false, $timeout=null)
Waits for the replica DBs to catch up to the master position.
Definition: GlobalFunctions.php:2718
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1007
Job
Class to both describe a background job and handle jobs.
Definition: Job.php:30
RenameuserSQL\actorMigrationWriteOld
static actorMigrationWriteOld()
Indicate whether we should still write old user fields.
Definition: RenameuserSQL.php:405
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2575
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:8480
DB_MASTER
const DB_MASTER
Definition: defines.php:26
RenameUserJob\run
run()
Run the job.
Definition: RenameUserJob.php:45
RenameUserJob\$actorMigratedColumns
static array $actorMigratedColumns
Core tables+columns that are being migrated to the actor table.
Definition: RenameUserJob.php:30
Title
Represents a title within MediaWiki.
Definition: Title.php:42