MediaWiki REL1_31
migrateActors.php
Go to the documentation of this file.
1<?php
25
26require_once __DIR__ . '/Maintenance.php';
27
35 public function __construct() {
36 parent::__construct();
37 $this->addDescription( 'Migrates actors from pre-1.31 columns to the \'actor\' table' );
38 $this->setBatchSize( 100 );
39 }
40
41 protected function getUpdateKey() {
42 return __CLASS__;
43 }
44
45 protected function doDBUpdates() {
47
49 $this->output(
50 "...cannot update while \$wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW\n"
51 );
52 return false;
53 }
54
55 $this->output( "Creating actor entries for all registered users\n" );
56 $end = 0;
57 $dbw = $this->getDB( DB_MASTER );
58 $max = $dbw->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
59 $count = 0;
60 while ( $end < $max ) {
61 $start = $end + 1;
62 $end = min( $start + $this->mBatchSize, $max );
63 $this->output( "... $start - $end\n" );
64 $dbw->insertSelect(
65 'actor',
66 'user',
67 [ 'actor_user' => 'user_id', 'actor_name' => 'user_name' ],
68 [ "user_id >= $start", "user_id <= $end" ],
69 __METHOD__,
70 [ 'IGNORE' ],
71 [ 'ORDER BY' => [ 'user_id' ] ]
72 );
73 $count += $dbw->affectedRows();
75 }
76 $this->output( "Completed actor creation, added $count new actor(s)\n" );
77
78 $errors = 0;
79 $errors += $this->migrateToTemp(
80 'revision', 'rev_id', [ 'revactor_timestamp' => 'rev_timestamp', 'revactor_page' => 'rev_page' ],
81 'rev_user', 'rev_user_text', 'revactor_rev', 'revactor_actor'
82 );
83 $errors += $this->migrate( 'archive', 'ar_id', 'ar_user', 'ar_user_text', 'ar_actor' );
84 $errors += $this->migrate( 'ipblocks', 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_by_actor' );
85 $errors += $this->migrate( 'image', 'img_name', 'img_user', 'img_user_text', 'img_actor' );
86 $errors += $this->migrate(
87 'oldimage', [ 'oi_name', 'oi_timestamp' ], 'oi_user', 'oi_user_text', 'oi_actor'
88 );
89 $errors += $this->migrate( 'filearchive', 'fa_id', 'fa_user', 'fa_user_text', 'fa_actor' );
90 $errors += $this->migrate( 'recentchanges', 'rc_id', 'rc_user', 'rc_user_text', 'rc_actor' );
91 $errors += $this->migrate( 'logging', 'log_id', 'log_user', 'log_user_text', 'log_actor' );
92
93 $errors += $this->migrateLogSearch();
94
95 return $errors === 0;
96 }
97
105 private function makeNextCond( $dbw, $primaryKey, $row ) {
106 $next = '';
107 $display = [];
108 for ( $i = count( $primaryKey ) - 1; $i >= 0; $i-- ) {
109 $field = $primaryKey[$i];
110 $display[] = $field . '=' . $row->$field;
111 $value = $dbw->addQuotes( $row->$field );
112 if ( $next === '' ) {
113 $next = "$field > $value";
114 } else {
115 $next = "$field > $value OR $field = $value AND ($next)";
116 }
117 }
118 $display = implode( ' ', array_reverse( $display ) );
119 return [ $next, $display ];
120 }
121
131 private function addActorsForRows(
132 IDatabase $dbw, $nameField, array &$rows, array &$complainedAboutUsers, &$countErrors
133 ) {
134 $needActors = [];
135 $countActors = 0;
136
137 $keep = [];
138 foreach ( $rows as $index => $row ) {
139 $keep[$index] = true;
140 if ( $row->actor_id === null ) {
141 // All registered users should have an actor_id already. So
142 // if we have a usable name here, it means they didn't run
143 // maintenance/cleanupUsersWithNoId.php
144 $name = $row->$nameField;
145 if ( User::isUsableName( $name ) ) {
146 if ( !isset( $complainedAboutUsers[$name] ) ) {
147 $complainedAboutUsers[$name] = true;
148 $this->error(
149 "User name \"$name\" is usable, cannot create an anonymous actor for it."
150 . " Run maintenance/cleanupUsersWithNoId.php to fix this situation.\n"
151 );
152 }
153 unset( $keep[$index] );
154 $countErrors++;
155 } else {
156 $needActors[$name] = 0;
157 }
158 }
159 }
160 $rows = array_intersect_key( $rows, $keep );
161
162 if ( $needActors ) {
163 $dbw->insert(
164 'actor',
165 array_map( function ( $v ) {
166 return [
167 'actor_name' => $v,
168 ];
169 }, array_keys( $needActors ) ),
170 __METHOD__
171 );
172 $countActors += $dbw->affectedRows();
173
174 $res = $dbw->select(
175 'actor',
176 [ 'actor_id', 'actor_name' ],
177 [ 'actor_name' => array_keys( $needActors ) ],
178 __METHOD__
179 );
180 foreach ( $res as $row ) {
181 $needActors[$row->actor_name] = $row->actor_id;
182 }
183 foreach ( $rows as $row ) {
184 if ( $row->actor_id === null ) {
185 $row->actor_id = $needActors[$row->$nameField];
186 }
187 }
188 }
189
190 return $countActors;
191 }
192
206 protected function migrate( $table, $primaryKey, $userField, $nameField, $actorField ) {
207 $complainedAboutUsers = [];
208
209 $primaryKey = (array)$primaryKey;
210 $pkFilter = array_flip( $primaryKey );
211 $this->output(
212 "Beginning migration of $table.$userField and $table.$nameField to $table.$actorField\n"
213 );
215
216 $dbw = $this->getDB( DB_MASTER );
217 $next = '1=1';
218 $countUpdated = 0;
219 $countActors = 0;
220 $countErrors = 0;
221 while ( true ) {
222 // Fetch the rows needing update
223 $res = $dbw->select(
224 [ $table, 'actor' ],
225 array_merge( $primaryKey, [ $userField, $nameField, 'actor_id' ] ),
226 [
227 $actorField => 0,
228 $next,
229 ],
230 __METHOD__,
231 [
232 'ORDER BY' => $primaryKey,
233 'LIMIT' => $this->mBatchSize,
234 ],
235 [
236 'actor' => [
237 'LEFT JOIN',
238 "$userField != 0 AND actor_user = $userField OR "
239 . "($userField = 0 OR $userField IS NULL) AND actor_name = $nameField"
240 ]
241 ]
242 );
243 if ( !$res->numRows() ) {
244 break;
245 }
246
247 // Insert new actors for rows that need one
248 $rows = iterator_to_array( $res );
249 $lastRow = end( $rows );
250 $countActors += $this->addActorsForRows(
251 $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
252 );
253
254 // Update the existing rows
255 foreach ( $rows as $row ) {
256 if ( !$row->actor_id ) {
257 list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
258 $this->error(
259 "Could not make actor for row with $display "
260 . "$userField={$row->$userField} $nameField={$row->$nameField}\n"
261 );
262 $countErrors++;
263 continue;
264 }
265 $dbw->update(
266 $table,
267 [
268 $actorField => $row->actor_id,
269 $nameField => '',
270 ],
271 array_intersect_key( (array)$row, $pkFilter ) + [
272 $actorField => 0
273 ],
274 __METHOD__
275 );
276 $countUpdated += $dbw->affectedRows();
277 }
278
279 list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
280 $this->output( "... $display\n" );
282 }
283
284 $this->output(
285 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
286 . "$countErrors error(s)\n"
287 );
288 return $countErrors;
289 }
290
307 protected function migrateToTemp(
308 $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
309 ) {
310 $complainedAboutUsers = [];
311
312 $newTable = $table . '_actor_temp';
313 $this->output(
314 "Beginning migration of $table.$userField and $table.$nameField to $newTable.$actorField\n"
315 );
317
318 $dbw = $this->getDB( DB_MASTER );
319 $next = [];
320 $countUpdated = 0;
321 $countActors = 0;
322 $countErrors = 0;
323 while ( true ) {
324 // Fetch the rows needing update
325 $res = $dbw->select(
326 [ $table, $newTable, 'actor' ],
327 [ $primaryKey, $userField, $nameField, 'actor_id' ] + $extra,
328 [ $newPrimaryKey => null ] + $next,
329 __METHOD__,
330 [
331 'ORDER BY' => $primaryKey,
332 'LIMIT' => $this->mBatchSize,
333 ],
334 [
335 $newTable => [ 'LEFT JOIN', "{$primaryKey}={$newPrimaryKey}" ],
336 'actor' => [
337 'LEFT JOIN',
338 "$userField != 0 AND actor_user = $userField OR "
339 . "($userField = 0 OR $userField IS NULL) AND actor_name = $nameField"
340 ]
341 ]
342 );
343 if ( !$res->numRows() ) {
344 break;
345 }
346
347 // Insert new actors for rows that need one
348 $rows = iterator_to_array( $res );
349 $lastRow = end( $rows );
350 $countActors += $this->addActorsForRows(
351 $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
352 );
353
354 // Update rows
355 if ( $rows ) {
356 $inserts = [];
357 $updates = [];
358 foreach ( $rows as $row ) {
359 if ( !$row->actor_id ) {
360 list( , $display ) = $this->makeNextCond( $dbw, [ $primaryKey ], $row );
361 $this->error(
362 "Could not make actor for row with $display "
363 . "$userField={$row->$userField} $nameField={$row->$nameField}\n"
364 );
365 $countErrors++;
366 continue;
367 }
368 $ins = [
369 $newPrimaryKey => $row->$primaryKey,
370 $actorField => $row->actor_id,
371 ];
372 foreach ( $extra as $to => $from ) {
373 $ins[$to] = $row->$to; // It's aliased
374 }
375 $inserts[] = $ins;
376 $updates[] = $row->$primaryKey;
377 }
378 $this->beginTransaction( $dbw, __METHOD__ );
379 $dbw->insert( $newTable, $inserts, __METHOD__ );
380 $dbw->update( $table, [ $nameField => '' ], [ $primaryKey => $updates ], __METHOD__ );
381 $countUpdated += $dbw->affectedRows();
382 $this->commitTransaction( $dbw, __METHOD__ );
383 }
384
385 // Calculate the "next" condition
386 list( $n, $display ) = $this->makeNextCond( $dbw, [ $primaryKey ], $lastRow );
387 $next = [ $n ];
388 $this->output( "... $display\n" );
389 }
390
391 $this->output(
392 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
393 . "$countErrors error(s)\n"
394 );
395 return $countErrors;
396 }
397
402 protected function migrateLogSearch() {
403 $complainedAboutUsers = [];
404
405 $primaryKey = [ 'ls_field', 'ls_value' ];
406 $pkFilter = array_flip( $primaryKey );
407 $this->output( "Beginning migration of log_search\n" );
409
410 $dbw = $this->getDB( DB_MASTER );
411 $countUpdated = 0;
412 $countActors = 0;
413 $countErrors = 0;
414
415 $next = '1=1';
416 while ( true ) {
417 // Fetch the rows needing update
418 $res = $dbw->select(
419 [ 'log_search', 'actor' ],
420 [ 'ls_field', 'ls_value', 'actor_id' ],
421 [
422 'ls_field' => 'target_author_id',
423 $next,
424 ],
425 __METHOD__,
426 [
427 'DISTINCT',
428 'ORDER BY' => [ 'ls_value' ],
429 'LIMIT' => $this->mBatchSize,
430 ],
431 [ 'actor' => [ 'LEFT JOIN', 'ls_value = ' . $dbw->buildStringCast( 'actor_user' ) ] ]
432 );
433 if ( !$res->numRows() ) {
434 break;
435 }
436
437 // Update the rows
438 $del = [];
439 foreach ( $res as $row ) {
440 $lastRow = $row;
441 if ( !$row->actor_id ) {
442 list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
443 $this->error( "No actor for row with $display\n" );
444 $countErrors++;
445 continue;
446 }
447 $dbw->update(
448 'log_search',
449 [
450 'ls_field' => 'target_author_actor',
451 'ls_value' => $row->actor_id,
452 ],
453 [
454 'ls_field' => $row->ls_field,
455 'ls_value' => $row->ls_value,
456 ],
457 __METHOD__,
458 [ 'IGNORE' ]
459 );
460 $countUpdated += $dbw->affectedRows();
461 $del[] = $row->ls_value;
462 }
463 if ( $del ) {
464 $dbw->delete(
465 'log_search', [ 'ls_field' => 'target_author_id', 'ls_value' => $del ], __METHOD__
466 );
467 $countUpdated += $dbw->affectedRows();
468 }
469
470 list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
471 $this->output( "... $display\n" );
473 }
474
475 $next = '1=1';
476 while ( true ) {
477 // Fetch the rows needing update
478 $res = $dbw->select(
479 [ 'log_search', 'actor' ],
480 [ 'ls_field', 'ls_value', 'actor_id' ],
481 [
482 'ls_field' => 'target_author_ip',
483 $next,
484 ],
485 __METHOD__,
486 [
487 'DISTINCT',
488 'ORDER BY' => [ 'ls_value' ],
489 'LIMIT' => $this->mBatchSize,
490 ],
491 [ 'actor' => [ 'LEFT JOIN', 'ls_value = actor_name' ] ]
492 );
493 if ( !$res->numRows() ) {
494 break;
495 }
496
497 // Insert new actors for rows that need one
498 $rows = iterator_to_array( $res );
499 $lastRow = end( $rows );
500 $countActors += $this->addActorsForRows(
501 $dbw, 'ls_value', $rows, $complainedAboutUsers, $countErrors
502 );
503
504 // Update the rows
505 $del = [];
506 foreach ( $rows as $row ) {
507 if ( !$row->actor_id ) {
508 list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
509 $this->error( "Could not make actor for row with $display\n" );
510 $countErrors++;
511 continue;
512 }
513 $dbw->update(
514 'log_search',
515 [
516 'ls_field' => 'target_author_actor',
517 'ls_value' => $row->actor_id,
518 ],
519 [
520 'ls_field' => $row->ls_field,
521 'ls_value' => $row->ls_value,
522 ],
523 __METHOD__,
524 [ 'IGNORE' ]
525 );
526 $countUpdated += $dbw->affectedRows();
527 $del[] = $row->ls_value;
528 }
529 if ( $del ) {
530 $dbw->delete(
531 'log_search', [ 'ls_field' => 'target_author_ip', 'ls_value' => $del ], __METHOD__
532 );
533 $countUpdated += $dbw->affectedRows();
534 }
535
536 list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
537 $this->output( "... $display\n" );
539 }
540
541 $this->output(
542 "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
543 . "$countErrors error(s)\n"
544 );
545 return $countErrors;
546 }
547}
548
549$maintClass = "MigrateActors";
550require_once RUN_MAINTENANCE_IF_MAIN;
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
wfWaitForSlaves( $ifWritesSince=null, $wiki=false, $cluster=false, $timeout=null)
Waits for the replica DBs to catch up to the master position.
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
beginTransaction(IDatabase $dbw, $fname)
Begin a transcation on a DB.
commitTransaction(IDatabase $dbw, $fname)
Commit the transcation on a DB handle and wait for replica DBs to catch up.
getDB( $db, $groups=[], $wiki=false)
Returns a database to be used by current maintenance script.
addDescription( $text)
Set the description text.
setBatchSize( $s=0)
Set the batch size.
Maintenance script that migrates actors from pre-1.31 columns to the 'actor' table.
addActorsForRows(IDatabase $dbw, $nameField, array &$rows, array &$complainedAboutUsers, &$countErrors)
Add actors for anons in a set of rows.
doDBUpdates()
Do the actual work.
migrate( $table, $primaryKey, $userField, $nameField, $actorField)
Migrate actors in a table.
migrateLogSearch()
Migrate actors in the log_search table.
__construct()
Default constructor.
makeNextCond( $dbw, $primaryKey, $row)
Calculate a "next" condition and a display string.
migrateToTemp( $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField)
Migrate actors in a table to a temporary table.
getUpdateKey()
Get the update key name to go in the update log table.
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:1018
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all. It could be easily changed to send incrementally if that becomes useful
the array() calling protocol came about after MediaWiki 1.4rc1.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition hooks.txt:2783
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults error
Definition hooks.txt:2612
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
const MIGRATION_WRITE_NEW
Definition Defines.php:304
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
affectedRows()
Get the number of rows affected by the last write query.
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
require_once RUN_MAINTENANCE_IF_MAIN
$maintClass
const DB_MASTER
Definition defines.php:29