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;
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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...
error( $err, $die=0)
Throw an error to the user.
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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const MIGRATION_WRITE_NEW
Definition Defines.php:304
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
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
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